Initial commit
This commit is contained in:
18
.claude-plugin/plugin.json
Normal file
18
.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "netresearch-skills-bundle",
|
||||
"description": "Temporary container plugin for skill visibility workaround (Claude Code issue #10568)",
|
||||
"version": "0.0.0-2025.11.28",
|
||||
"author": {
|
||||
"name": "Netresearch DTT GmbH",
|
||||
"email": "info@netresearch.de"
|
||||
},
|
||||
"skills": [
|
||||
"./skills/typo3-docs",
|
||||
"./skills/typo3-testing",
|
||||
"./skills/typo3-ddev",
|
||||
"./skills/typo3-core-contributions",
|
||||
"./skills/typo3-conformance",
|
||||
"./skills/netresearch-branding",
|
||||
"./skills/agents"
|
||||
]
|
||||
}
|
||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# netresearch-skills-bundle
|
||||
|
||||
Temporary container plugin for skill visibility workaround (Claude Code issue #10568)
|
||||
744
plugin.lock.json
Normal file
744
plugin.lock.json
Normal file
@@ -0,0 +1,744 @@
|
||||
{
|
||||
"$schema": "internal://schemas/plugin.lock.v1.json",
|
||||
"pluginId": "gh:netresearch/claude-code-marketplace:netresearch-skills-bundle",
|
||||
"normalized": {
|
||||
"repo": null,
|
||||
"ref": "refs/tags/v20251128.0",
|
||||
"commit": "1e84fc21f2f1f1032570653f85f86f4214bbffb1",
|
||||
"treeHash": "2dac5be64a7d70361a08099a0ed7fd471693e4f6b4405d6c25731205d9e9b3ba",
|
||||
"generatedAt": "2025-11-28T10:27:20.800627Z",
|
||||
"toolVersion": "publish_plugins.py@0.2.0"
|
||||
},
|
||||
"origin": {
|
||||
"remote": "git@github.com:zhongweili/42plugin-data.git",
|
||||
"branch": "master",
|
||||
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
|
||||
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
|
||||
},
|
||||
"manifest": {
|
||||
"name": "netresearch-skills-bundle",
|
||||
"description": "Temporary container plugin for skill visibility workaround (Claude Code issue #10568)"
|
||||
},
|
||||
"content": {
|
||||
"files": [
|
||||
{
|
||||
"path": "README.md",
|
||||
"sha256": "02b0131ab35e7f80600c283a7b93f2d134f59068fed4b227ba367059a4af65d7"
|
||||
},
|
||||
{
|
||||
"path": ".claude-plugin/plugin.json",
|
||||
"sha256": "588cdc173df1df42e067d518fed06e6fc6424c7c6bf60cd093e7bc7314713024"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-testing/LICENSE",
|
||||
"sha256": "62e5db1ab91125b874e6408a65301f10802d8204c06b3047ec07f84d7db3f471"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-testing/README.md",
|
||||
"sha256": "0ffd450890029269e5dae36dbabb6c6fcd2c2ef469dfc326058945af4bba6a26"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-testing/.gitignore",
|
||||
"sha256": "db6cc676b541ea3d6987d9e5d662e998f36a146eba3abd14b5e65cab025fc389"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-testing/SKILL.md",
|
||||
"sha256": "8d3779f65441ab80e7c3c5f3b6f4cdee483e9d1eced56a7820330cccb79ad488"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-testing/references/quality-tools.md",
|
||||
"sha256": "d186766c200a72755ea9456677a8a26632b6fa5093aad847f9c4463ea56aac15"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-testing/references/test-runners.md",
|
||||
"sha256": "ea26c2ddcc6abc23a5206665aa39c053250d09cc21c3b172f9c08f78727f4017"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-testing/references/acceptance-testing.md",
|
||||
"sha256": "2eb3373a49f4bd584d6af026406fe89b77bf53eaf6250a5e739036fe681152b1"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-testing/references/functional-testing.md",
|
||||
"sha256": "7a5f94e262f76b533328e6522cfce2730e03c5d4718b20dc82bf9427e59bca4f"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-testing/references/unit-testing.md",
|
||||
"sha256": "d284a11aea0f9f74c65de21028ad1e0312a23d8f2b4a6f02d71383908a8f7a24"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-testing/references/javascript-testing.md",
|
||||
"sha256": "d912bf3a55c3fde7d5b35dd6b8d217a2c92dc56cbce2aa833b97cbf4f930c090"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-testing/references/ci-cd.md",
|
||||
"sha256": "cc63f64bb0b3cc59e08c145a27e5f4a22a74ffb23c7d07d11b43276e60872fb5"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-testing/claudedocs/refactoring-summary.md",
|
||||
"sha256": "23e8c66839a7c3dc5f277a82eccb2d74dcea57ebf992ca3e01a519270613e7f5"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-testing/scripts/validate-setup.sh",
|
||||
"sha256": "c44fa4a6b8b0e88e6d2bbbdd4415279635a6478dd9f1e599cb27f890fef8ef92"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-testing/scripts/setup-testing.sh",
|
||||
"sha256": "e19c01d4c34abd3baa6afd6674e15095d1666909bac602273ac666f34a50e1db"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-testing/scripts/generate-test.sh",
|
||||
"sha256": "e72909650ac0cc12e9483e0ec7a84da863dce99882fa12410035460fc1c93cd3"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-testing/templates/FunctionalTestsBootstrap.php",
|
||||
"sha256": "60caec69cc3792c3f22184c627eb6f597e57a387c1646e2277148e877f66aa0c"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-testing/templates/UnitTests.xml",
|
||||
"sha256": "61cd16d938ba8fbb7d49c21a444fb86766ce65449f66e3d9a12135451a174786"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-testing/templates/github-actions-tests.yml",
|
||||
"sha256": "8da189186487e10e5d5ddf0c51302138c169220a2c11306c4eab345230853d41"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-testing/templates/AGENTS.md",
|
||||
"sha256": "9a0c9055ce3404dca8f3913515de25698030855e5583367450aae2eb22c56e17"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-testing/templates/FunctionalTests.xml",
|
||||
"sha256": "8b73dedafa787328d263b46661bb4df91850e60ed12d2a33c5f493b828e79f9a"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-testing/templates/docker/codeception.yml",
|
||||
"sha256": "85c24f53e5a1f7e959312d141fd62261f0440be3ca70a45896082e0b5353dd2b"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-testing/templates/docker/docker-compose.yml",
|
||||
"sha256": "2693efec58eb5ddebd218d2a452581f21c02397fa214fea3ae46e677516881dd"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-testing/templates/example-tests/ExampleAcceptanceCest.php",
|
||||
"sha256": "30e158149508c15ecb75de850d8b6f9b09baad0014d6ab0183aba249dbd03d10"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-testing/templates/example-tests/ExampleFunctionalTest.php",
|
||||
"sha256": "81de444c741d8f05ef9abba8b005d32474da9b77a0a0aefe559350acd5f2b428"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-testing/templates/example-tests/ExampleUnitTest.php",
|
||||
"sha256": "2c68c617a1c70f8a896c252c4aa0ed5578af5bcc7c0e7cc49257866da05fb11c"
|
||||
},
|
||||
{
|
||||
"path": "skills/netresearch-branding/README.md",
|
||||
"sha256": "5dfbc0502098d2c9d44de543a6b62a82e4c34bc34c5d6d2cfede71c21622150c"
|
||||
},
|
||||
{
|
||||
"path": "skills/netresearch-branding/SKILL.md",
|
||||
"sha256": "4dbc3494d69303bf5be910168bd991bfffa75c0d4f0606274e333f4f1349a032"
|
||||
},
|
||||
{
|
||||
"path": "skills/netresearch-branding/references/colors.md",
|
||||
"sha256": "42fbf46dc515b079a250727b8881dde7541400d1777eccfd07b910c6a43328f8"
|
||||
},
|
||||
{
|
||||
"path": "skills/netresearch-branding/references/web-design.md",
|
||||
"sha256": "2016875af8aaca78eff4121e107871339d79441d0e23d4ff3f148dff3fc12dd9"
|
||||
},
|
||||
{
|
||||
"path": "skills/netresearch-branding/references/typography.md",
|
||||
"sha256": "a2484d7416e8f35e1db48dc694fe56945819951e2e8b9ad9b6d1815627b29fff"
|
||||
},
|
||||
{
|
||||
"path": "skills/netresearch-branding/claudedocs/refactoring-summary.md",
|
||||
"sha256": "c1ace25fce5c5c8afa76d0900c8fec9c8255618c845a32b5eb5adce6803b260f"
|
||||
},
|
||||
{
|
||||
"path": "skills/netresearch-branding/examples/components.html",
|
||||
"sha256": "548341dfdbaacf0b0d8befc95b264f4fdde1793dc6d3f73e1edde038624591f5"
|
||||
},
|
||||
{
|
||||
"path": "skills/netresearch-branding/templates/styles.css",
|
||||
"sha256": "f4c1ecdb68b4132130f4f6261e56c1b020fdcbe38cf4ad4e4bff85dded88cc79"
|
||||
},
|
||||
{
|
||||
"path": "skills/netresearch-branding/templates/landing-page.html",
|
||||
"sha256": "d5ad833e350e48a1e8cac4c8aa461e293e9723fab60469a8fc3b912046708fed"
|
||||
},
|
||||
{
|
||||
"path": "skills/netresearch-branding/assets/logos/netresearch-symbol-only.svg",
|
||||
"sha256": "b63c3f838232beff165668516c1a2a81f9c05327b3712c07453523e4055337c1"
|
||||
},
|
||||
{
|
||||
"path": "skills/agents/LICENSE",
|
||||
"sha256": "b4aa20b52533f8ab363e465f07d9e26ad92dac6e250b650c8947c99d6ee466e0"
|
||||
},
|
||||
{
|
||||
"path": "skills/agents/STATUS.md",
|
||||
"sha256": "a656b07890efb13577aa36e08becaded05e86e72e4fe09cc147d2edff921b71d"
|
||||
},
|
||||
{
|
||||
"path": "skills/agents/README.md",
|
||||
"sha256": "b8521f379edf306b0fd5edb2567859bf52185585464a334cfe2d9a74a9bda56f"
|
||||
},
|
||||
{
|
||||
"path": "skills/agents/.gitignore",
|
||||
"sha256": "03d5d046fa175e5d6ea564d562ffbd2a555e964b0a91d3ce04af8bc54f3a54ab"
|
||||
},
|
||||
{
|
||||
"path": "skills/agents/SKILL.md",
|
||||
"sha256": "ee7db7e9bdc267a2ae2e45fae3996af707eee3be49b9a9387e1286c449d642a2"
|
||||
},
|
||||
{
|
||||
"path": "skills/agents/composer.json",
|
||||
"sha256": "0c5d90b317084fbadf5b68d7c82f384ea38fb7077ad2f53a3eccd1e923c0b85d"
|
||||
},
|
||||
{
|
||||
"path": "skills/agents/references/analysis.md",
|
||||
"sha256": "2a24ab56d49577acfd6818a7243fd3ef1dc518d55196a379deacace140a9012c"
|
||||
},
|
||||
{
|
||||
"path": "skills/agents/references/examples/coding-agent-cli/scripts-AGENTS.md",
|
||||
"sha256": "fb68e9b8fdd0a38887edb23adf8a80270635b6d66c6063b5e5c9049217193aca"
|
||||
},
|
||||
{
|
||||
"path": "skills/agents/references/examples/coding-agent-cli/AGENTS.md",
|
||||
"sha256": "0012cbc25474d641e1f43714e4728082a2f8106323880fe91959f4b785eeb7ef"
|
||||
},
|
||||
{
|
||||
"path": "skills/agents/references/examples/ldap-selfservice/internal-AGENTS.md",
|
||||
"sha256": "587e67c42b0b273c3a1a7291c1cd481380aeac33693e0314c48908344efa3c5f"
|
||||
},
|
||||
{
|
||||
"path": "skills/agents/references/examples/ldap-selfservice/internal-web-AGENTS.md",
|
||||
"sha256": "fe8aaf9130a6f35e4a93dd91fd477088fe0e6fc8d9b046473b02c7491ef7774e"
|
||||
},
|
||||
{
|
||||
"path": "skills/agents/references/examples/ldap-selfservice/AGENTS.md",
|
||||
"sha256": "12e0d8102ff9a8d9afe58a6c62f5f80dfbab6a852a812d9d14ffbc2cf6167f7e"
|
||||
},
|
||||
{
|
||||
"path": "skills/agents/references/examples/t3x-rte-ckeditor-image/Classes-AGENTS.md",
|
||||
"sha256": "18c70510d702bdb19f0635cf56b7fd1d43f33b261195c08a076bda7cdb6e47ef"
|
||||
},
|
||||
{
|
||||
"path": "skills/agents/references/examples/t3x-rte-ckeditor-image/AGENTS.md",
|
||||
"sha256": "07b74ea78fa432057a660207cf0359a9fccab3823329a47a506c77f2b2e71d7a"
|
||||
},
|
||||
{
|
||||
"path": "skills/agents/references/examples/simple-ldap-go/examples-AGENTS.md",
|
||||
"sha256": "883911058ad0c8c48da8486897c90f029a19658986f017bf1bde5ddc54eeffc7"
|
||||
},
|
||||
{
|
||||
"path": "skills/agents/references/examples/simple-ldap-go/AGENTS.md",
|
||||
"sha256": "b7f17e8beca30c42f40a7285d6c0043a67807181de5703aff2bce9e9c73ab072"
|
||||
},
|
||||
{
|
||||
"path": "skills/agents/claudedocs/refactoring-summary.md",
|
||||
"sha256": "eddf1114e3997c29e57d31133cf5a98e5cac0eabdefb06897328851f7b67cedd"
|
||||
},
|
||||
{
|
||||
"path": "skills/agents/scripts/validate-structure.sh",
|
||||
"sha256": "60789bd917958d125638f94479e6501e51d2d0f5d5373a4e33970f5f7710c848"
|
||||
},
|
||||
{
|
||||
"path": "skills/agents/scripts/detect-scopes.sh",
|
||||
"sha256": "a02b0147487009e754b658f84d48c47b4a45e46a0345616686a2aaf72bbe7967"
|
||||
},
|
||||
{
|
||||
"path": "skills/agents/scripts/extract-commands.sh",
|
||||
"sha256": "0ee80d781d8b031a0c626da4206321c747084b675c70fb8cc3e1303b2c1b4fc5"
|
||||
},
|
||||
{
|
||||
"path": "skills/agents/scripts/generate-agents.sh",
|
||||
"sha256": "3d50161327537782bde5d322e9e5fb6c83cd059e2d487212bcee0c72dd2dfe7a"
|
||||
},
|
||||
{
|
||||
"path": "skills/agents/scripts/detect-project.sh",
|
||||
"sha256": "403b1324a978ec99c0c99c70314de1b97136cc3dd23eb131e2e72389d7a5980c"
|
||||
},
|
||||
{
|
||||
"path": "skills/agents/scripts/lib/template.sh",
|
||||
"sha256": "4b30b4d85a30eea9ff5f9a36085b34a757396fde3058c734c869c640b18d9e17"
|
||||
},
|
||||
{
|
||||
"path": "skills/agents/templates/root-verbose.md",
|
||||
"sha256": "de9d263062471fc8f1fc154729a9a521acef48fda771ffee15f8a1803fd86e2c"
|
||||
},
|
||||
{
|
||||
"path": "skills/agents/templates/root-thin.md",
|
||||
"sha256": "686a97e5d5dd69ecbf567059167b35dde87a0691d6c5f69eac0759039b0bf71d"
|
||||
},
|
||||
{
|
||||
"path": "skills/agents/templates/scoped/backend-go.md",
|
||||
"sha256": "f1aad3498ac26177fdbbe69d07196b17b129fde9538e3d600754df686f26ee9d"
|
||||
},
|
||||
{
|
||||
"path": "skills/agents/templates/scoped/cli.md",
|
||||
"sha256": "c54098b6a5547639c7abc2403994495b96e61072b4e94d3bcb1bec749a611201"
|
||||
},
|
||||
{
|
||||
"path": "skills/agents/templates/scoped/frontend-typescript.md",
|
||||
"sha256": "c8024e783a228ad367679b2f961760fd3bf79bfb3b290c9c9d70c197626867a1"
|
||||
},
|
||||
{
|
||||
"path": "skills/agents/templates/scoped/backend-php.md",
|
||||
"sha256": "8956f7006f8a8fc13da61e9be3abde9711c7fd94fe533dd8fdfa8b3a966a6c80"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/LICENSE",
|
||||
"sha256": "ab373f9ef890455f235a4ccfab7ae7f1157d5edd45e659abae687b3b6255a8d6"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/README.md",
|
||||
"sha256": "fc05bd45dbda4ad94bfce6cbb2ddc745810328c95a6c1ab011e85b288b58fe51"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/.gitignore",
|
||||
"sha256": "593d792f248e0cbea5d41215b10771ad52198bc565f0f20b640e9be3eecacf53"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/SKILL.md",
|
||||
"sha256": "bfff69ac938e2e64d5d51056f8937eef72607a784c54f1925b711bbf04262f10"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/references/windows-optimizations.md",
|
||||
"sha256": "7eae23d263c5b026b183c2e42542b2efaa449ca9ad9c996768ca0c0e5cd08e5f"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/references/0001-valkey-default-with-redis-alternative.md",
|
||||
"sha256": "ba2b2598645132dec697fd19c7d838d503bfc22480daf5c249e105dd55be54fa"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/references/windows-fixes.md",
|
||||
"sha256": "e6ad59d54c2198f266f169afdf23f717d4f0865fecb95f927a93651db8904a74"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/references/troubleshooting.md",
|
||||
"sha256": "f242014dc3ee7088e43484aeaa79899d4afc09bd11936e28a72a6854d601a21a"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/references/advanced-options.md",
|
||||
"sha256": "91a5afc978a25e97208b781781f0eaeb08b11b8ee92db64f9d4e37caaa46200b"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/references/prerequisites-validation.md",
|
||||
"sha256": "cf809654a94cb872202843b9f4a95d9f3b1ae6dfeb1efa9d0b6569f11d0b1d42"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/references/quickstart.md",
|
||||
"sha256": "cc7ebde884323fcae12df2699dcdcf1e140b0b62117a8f7658dcc42fa3c68b34"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/references/index-page-generation.md",
|
||||
"sha256": "a667a23a99423bcdb1b6ee3c6e5782c4717dd679292b85af9dcbe321ca98cac7"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/references/0002-mariadb-default-with-database-alternatives.md",
|
||||
"sha256": "aec6ece3c744066b63023e40df82369e0719a99e988a2014414602f21cc16b21"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/claudedocs/skill-refactoring-summary.md",
|
||||
"sha256": "382d6d27e41b5395ddf31134f6d2ae787f761bb4870ac2e22a770a3846eb6a6c"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/scripts/validate-prerequisites.sh",
|
||||
"sha256": "5c1401510f9986e343749a56a8fa64f8046946637362848284748e4fb8aa01b3"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/assets/templates/docker-compose.git-info.yaml",
|
||||
"sha256": "c88eec7b034e5ff2c59e2bc4f1f99da7b768a8c917f8f53437786031c0891825"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/assets/templates/config.redis.php.example",
|
||||
"sha256": "d30b42cedfa7e6b1939518d91ff9a85959d137bb56ecae0616982917f7b07fbc"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/assets/templates/.envrc",
|
||||
"sha256": "35911511432d6c5fbbd00dc9b1f25d17658e30ddf1a44cd8da17b3c0d4e75cb6"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/assets/templates/docker-compose.web.yaml",
|
||||
"sha256": "703079576c5a3c970d3dedec4968de4391654f34834f7d1c36b3c4b147b6f23f"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/assets/templates/Makefile.template",
|
||||
"sha256": "d39015650acc88a43f89983de4cb141766f92bbf921269f439da53615b372986"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/assets/templates/docker-compose.ofelia.yaml.optional",
|
||||
"sha256": "b664d9d7b69c3ce7855603e1fa2c77f930a447a73fcfdfaceb845c24242f0b4b"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/assets/templates/index.html.template",
|
||||
"sha256": "b3c905e7b64dc1284ec8f5013c04e9c12180f75031d69232694538c601828bcf"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/assets/templates/config.yaml",
|
||||
"sha256": "3a83be64a83f6e50aedca7320c31e6a80612d12b3e65b1569324906ec1cc31f6"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/assets/templates/README-SERVICES.md.optional",
|
||||
"sha256": "20f1d7379ff9115b53187154b3afcab6dab2bc81811b5392a81c02a9cf6bbafa"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/assets/templates/docker-compose.services.yaml.optional",
|
||||
"sha256": "fd2a75d4b5c2861367dd0e63cf09e9be31a52f5c4990d38c21d6471bb3a44fe1"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/assets/templates/docker-compose.services-redis.yaml.optional",
|
||||
"sha256": "564fde7771013bc18808dde014e29621c68c826ef07f8957482ab6b1d31c0ccc"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/assets/templates/web-build/install-cron.sh.optional",
|
||||
"sha256": "ef1cba4123b9df86aedb15509c393e198300cf2ae92cb9aef4b5e3284af8ca00"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/assets/templates/web-build/Dockerfile",
|
||||
"sha256": "518c5065f73bdf796d412efd84db4192fe6c486c08741d576a144cc82755a436"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/assets/templates/homeadditions/.bashrc_additions.optional",
|
||||
"sha256": "5b8046dbf3974f89227b3f8d87bf4089183333a3c83f454925c5fdb340e8bbf9"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/assets/templates/commands/install-v11",
|
||||
"sha256": "6e80f13afd7b64883db9539c9d79d8443b0d134cf8bd7f78520a8f6cbf337689"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/assets/templates/commands/install-v13",
|
||||
"sha256": "f87b775f3822318876d0db63c78cbc8dad48e4e37f61d7958ae76ac90a81d9ce"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/assets/templates/commands/install-v12",
|
||||
"sha256": "f50abab5b2e0446d5a5da6dce3f4b6d801e9823773b435df19f17a349f8f3a7a"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/assets/templates/commands/install-all",
|
||||
"sha256": "2ba13bf400536925fc9e9faba5127031423e8ba32feed76a643c8343bb79e922"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/assets/templates/commands/host/setup",
|
||||
"sha256": "a4dfdd5540a4662e58f125e4ca72b685d783a50275971d23bbf5c95b1240038f"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/assets/templates/commands/host/docs",
|
||||
"sha256": "13545bb03d87dd11dba24131753b66db758862c96986df40f48555f7d4786908"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/assets/templates/commands/host/pre-start-git-info",
|
||||
"sha256": "1579befd98de8d00ad8d62b7d96b4ec36a0d7fe97084dc55761e100d56f876c2"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/assets/templates/commands/web/generate-index",
|
||||
"sha256": "3ad42122b0c027a1a9b028af3fda855518f15763c7aa5adfc6bc4d996f3b7bd6"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/assets/templates/commands/web/configure-extension.optional",
|
||||
"sha256": "400cdb85bf7ef285556e6d337e25e4636a8bb4dbb222fcb1672f3374f19e404e"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/assets/templates/commands/web/generate-makefile",
|
||||
"sha256": "8fee0d248e9b63e86a4a8fa29f0c5b121cd22ead08f12287e827a114d45fc664"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/assets/templates/commands/web/install-introduction.optional",
|
||||
"sha256": "3f2a8f3605795cdd69b52760a8c2c9952c38fcfcf267815490deb1b7c4bb58ed"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-ddev/assets/templates/apache/apache-site.conf",
|
||||
"sha256": "f40f09c69e02e8855fc30416a55bd3059ddedfbb2a727c853d7a89f00d5cbd60"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-core-contributions/LICENSE",
|
||||
"sha256": "ab373f9ef890455f235a4ccfab7ae7f1157d5edd45e659abae687b3b6255a8d6"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-core-contributions/README.md",
|
||||
"sha256": "0433a370f77599dacfd39053cec3cb17118ae46c4f72a0114fafda1526bc6dcb"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-core-contributions/.gitignore",
|
||||
"sha256": "3f62ab1e721fdb95478da9f8f9da4cecdc319027d5bd50875be155096894577b"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-core-contributions/SKILL.md",
|
||||
"sha256": "be04cc864305cd81f7c453e438b7561b9d6f55b2d6a08f97dc16de977c01525c"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-core-contributions/references/commit-message-format.md",
|
||||
"sha256": "9b2f6e02622990792e3fb3dd9b3517bdf589fdf617177db3e62e8cdb8435ade4"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-core-contributions/references/ddev-setup-workflow.md",
|
||||
"sha256": "d4d7ce7295626390c0a7e5d0de001905d8f1eba58c412f6f2fcf2d9756b549b8"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-core-contributions/references/troubleshooting.md",
|
||||
"sha256": "5f2fa7356654dc27b7f49c6472ab5548b86927aad8c2452889cc04897fb69ef9"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-core-contributions/references/forge-api.md",
|
||||
"sha256": "e7a0fc7dbe169392e851441f9f780de4015040db4bc004a418357ae157359d26"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-core-contributions/references/gerrit-workflow.md",
|
||||
"sha256": "4495e847cf2c2865216b54670446060d721d879609a824e6a6b2ce8ec4b93c81"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-core-contributions/references/commit-msg-hook.md",
|
||||
"sha256": "1db8b73f5ffcb3c09a23035c7b772b3db5471c07cdd4a9e3a65c2f6420581896"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-core-contributions/references/account-setup.md",
|
||||
"sha256": "9dc89d22b729d21cf09efb6aa848237c641faa84a2c9ab234cb5297d07231756"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-core-contributions/references/gerrit-review-patterns.md",
|
||||
"sha256": "585195c4184806acdf01e3437dcc9b97de0c7104c86886242067bbf08836de14"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-core-contributions/claudedocs/refactoring-summary.md",
|
||||
"sha256": "0feaaf54fc2738956bf5c34829a7093a562ddb7d9f1ab468f0198a40992a347c"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-core-contributions/scripts/setup-typo3-coredev.sh",
|
||||
"sha256": "d47c65f4cd00aab4a1c0b46323a8e96ba1c2e1d14992f94d6dce798f8113c7b0"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-core-contributions/scripts/create-commit-message.py",
|
||||
"sha256": "c0ea29d591cbeefe21ec7a8375829bd870b6c271ff50d3f7e12717004974c28a"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-core-contributions/scripts/query-forge-metadata.sh",
|
||||
"sha256": "aa747ec03f3fc0a8842bd1bff236ae7253ded95f24f70e6c10e7567328a40aec"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-core-contributions/scripts/verify-prerequisites.sh",
|
||||
"sha256": "533f0555ec0f2a23088c5d11dce6193175439a68405faf2b6f5eef4251766f19"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-core-contributions/scripts/create-forge-issue.sh",
|
||||
"sha256": "60da8c26d4fde62279923281e402ada6a81a61865e9f55b4081188b74d3a97fc"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-core-contributions/scripts/validate-commit-message.py",
|
||||
"sha256": "3b75909562f243565927169dccaaf6c9fd5241e6bfd4b571a81aa5bca3b42db9"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-core-contributions/assets/commit-template.txt",
|
||||
"sha256": "9ff82d4769378bd677c2cccf167452040474c219991680a0b674b103fea9d681"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-docs/LICENSE",
|
||||
"sha256": "83edab32209ba0c16c0c48a2fe26fff50845567cb9f6b4da15cdfb833f000465"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-docs/README.md",
|
||||
"sha256": "58f342349d0d552d247c206c03815388eeb039887c24139716a8322fa7689b7a"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-docs/.gitignore",
|
||||
"sha256": "78e70ee14ad1b25efd9dc634bbc6789105c9b58590a0ee723e61a16acd1905f2"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-docs/SKILL.md",
|
||||
"sha256": "9602724068e345b8b5992887a0bf53ec23758b69ab6f2e7cccdff8d2ecc65aef"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-docs/references/documentation-coverage-analysis.md",
|
||||
"sha256": "7af86f9dbc003566e17e8474abf6ba2de42d6a30c8ec8a4c55d1df6ab0ab0924"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-docs/references/intercept-deployment.md",
|
||||
"sha256": "2ed547dc4b75bdceaf14a9191ad8fa4705485ddec3d8b03e11a68d7f5dd8b095"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-docs/references/extraction-patterns.md",
|
||||
"sha256": "4923178cb9513c7f1bfc891bb426d086084c4137cb6b76606bd44957d9c309ea"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-docs/references/typo3-extension-architecture.md",
|
||||
"sha256": "301e42f88e60c124c3c66e0ca800a379d80ba9664187d690b7989e9b15a20a5c"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-docs/references/rst-syntax.md",
|
||||
"sha256": "c1ad74539a0bb59d270dc4f6628ab481b3d72aa86c55a7717a3b60a70166df1e"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-docs/references/typo3-directives.md",
|
||||
"sha256": "3986e558ff5c7c260051897613d73e3debd8a49d791c186c928e6761b29fa816"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-docs/scripts/extract-composer.sh",
|
||||
"sha256": "1085c1bf7cba1d98a7ee0b3ad0a3326f8490a209e069dc7c3ee709fa4a32ea0b"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-docs/scripts/add-agents-md.sh",
|
||||
"sha256": "6dd85ef682d34d36266c50cd04debe7663aa3b70f5d23a56dfc16d946ffbec52"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-docs/scripts/extract-php.sh",
|
||||
"sha256": "e0f67003b1b4b6744f940a538c144394f9685bfd92f87281a2758564f379b023"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-docs/scripts/render_docs.sh",
|
||||
"sha256": "4992904d50c3b67a0a5c0b122bfba185b80d10814653dbdb236c6b03ad9f5322"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-docs/scripts/extract-repo-metadata.sh",
|
||||
"sha256": "0d41fe60e9945ddf749cef361b518a6cfdb68f31507fa0ab4ef3962f8829355d"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-docs/scripts/extract-extension-config.sh",
|
||||
"sha256": "9670d9c8f23c8f5666eb5ca2aec23ef0a94f86c5c53c2ed8d2be9cec29d1fc61"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-docs/scripts/extract-all.sh",
|
||||
"sha256": "391d6798db12db06c18088582d6a6264ce5d1d25ecf37e6ee3ab4a1e5de9027f"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-docs/scripts/extract-project-files.sh",
|
||||
"sha256": "013930097424d8d00a7ece3ff9703e8ffcaa2439264627c252e2fb02dc7a9958"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-docs/scripts/analyze-docs.sh",
|
||||
"sha256": "82efbe1190502dcf31ddc09e80ffd22e710706ffa359da85dd24f457f6634954"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-docs/scripts/validate_docs.sh",
|
||||
"sha256": "92620e2cd268a896d3362964f6a47db4124fb291e88a0f3f33fe3274f2ceb8f1"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-docs/scripts/extract-build-configs.sh",
|
||||
"sha256": "019921be95911ca275e6afb42bea36758c4f4ed9f893b2894f9ffb64f53a9b5c"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-docs/templates/AGENTS.md",
|
||||
"sha256": "170e12674522df8099c2b46917c4dc21b8df0882c3bc21a0ac61e539c7910649"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-conformance/README.md",
|
||||
"sha256": "5d13579ebff7c20fe0c59538aba85ce77763b3d7d59c5d3bffcc0ba20cf254ea"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-conformance/.gitignore",
|
||||
"sha256": "d2dbcbb13e12a322c8987474608144d4852424e8db305fa8eacf5cddfc8d50da"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-conformance/SKILL.md",
|
||||
"sha256": "68b223fd999a61f844e53158d7c0d2acaf3f2bb4188326ec9daf29c45ddad417"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-conformance/references/v13-deprecations.md",
|
||||
"sha256": "a6374eaf550050c52292a7d6187d044a7990222ec107f0f076948e4c4498d224"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-conformance/references/backend-module-v13.md",
|
||||
"sha256": "45c2a675563204470367611ab401768e52efd82539190e5d2037b09e71e1d16f"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-conformance/references/ext-files-validation.md",
|
||||
"sha256": "14856d9c803ca69d8c9628c86297e88b19b48dc12d409d7aae8a59b5956c6fd6"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-conformance/references/best-practices.md",
|
||||
"sha256": "305184bfe93aaa6ccada0fb278734a40c192301dfea7dad20163195021f5c1c6"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-conformance/references/directory-structure.md",
|
||||
"sha256": "cae94c8f7b5895b2b7ac8c756caa13eb2849858e1e95b2d2d1d4044b255364e5"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-conformance/references/crowdin-integration.md",
|
||||
"sha256": "92625735ff042ad0e99e0ec40732a430b2777653a53f15f381f5f725a54633ed"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-conformance/references/excellence-indicators.md",
|
||||
"sha256": "9cffb3cb62597c0b3b00624ed580de4d90aec51a50ec513a5e9823981c6b5f32"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-conformance/references/testing-standards.md",
|
||||
"sha256": "54ae9cfe6e1e682898f31a7756ca99e79a5d3f982c51646cc5b9dc030332ed1a"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-conformance/references/composer-validation.md",
|
||||
"sha256": "6542761396e7bc706925a84745e043e46d310d10d9e2a3ebfb754cd69e369c3e"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-conformance/references/extension-architecture.md",
|
||||
"sha256": "4023d9dc4294c464ab48b703d452568c5b42ad3c86665e97bbe0e1ef4851c00c"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-conformance/references/ext-emconf-validation.md",
|
||||
"sha256": "991b455b6eb042828c143aa9c08b725106979d7590d7b9b0adf9af5305013824"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-conformance/references/coding-guidelines.md",
|
||||
"sha256": "30388716f84210020d177abfcdf6a28b4b78b35e5b609d7d82c7eaa222348302"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-conformance/references/runtests-validation.md",
|
||||
"sha256": "e6cb139eb19592c42d8b1c87950632245e67fb34476110d63d7d70358900858b"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-conformance/references/version-requirements.md",
|
||||
"sha256": "daa17739122f2e1621f01fefc8b1389729b355aafba4258f182c65e127931589"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-conformance/references/hooks-and-events.md",
|
||||
"sha256": "84b4b535d3ac6f8a7908a576cda9f1a05b3658804fcd3130ebd5f33f7d20b59e"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-conformance/references/php-architecture.md",
|
||||
"sha256": "a39281ae23be135cff2791780cc4b8e8d6b2584d64776e66f901649c440644fe"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-conformance/references/development-environment.md",
|
||||
"sha256": "6036bfb46a66e58fdb8f22f99461fa50a92582e1e788c960742dee1c1b60456d"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-conformance/scripts/check-file-structure.sh",
|
||||
"sha256": "0da9d33a9d83adda445785fbe04a12345da4df802e700702867a8ac16d06102d"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-conformance/scripts/generate-report.sh",
|
||||
"sha256": "5792c0770a616fa9b23073005626a46e81d85c14c05c46c77aeca2c7b84bd832"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-conformance/scripts/check-architecture.sh",
|
||||
"sha256": "8e1fb2c2621c701ddd054dbb102fb43aa41c2541b45a14b4f18555c270b1d7a6"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-conformance/scripts/check-conformance.sh",
|
||||
"sha256": "dcc37ae3a2d5eb50197e020d93be9e331348df82ae50fac3817400ca7b7cebb9"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-conformance/scripts/check-phpstan-baseline.sh",
|
||||
"sha256": "e297e01caee0ecb47715e2c4631ba03b62e1fb9e24567c58d6c23f3539841735"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-conformance/scripts/check-testing.sh",
|
||||
"sha256": "70c8aaaa31b0d708424991c8a5e66786ff472bda356efedf8a75a579e7592e97"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-conformance/scripts/check-coding-standards.sh",
|
||||
"sha256": "22d162f56484650d1a801f7e418103408dfb643086c36d1684f289f4d5a81dd4"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-conformance/.github/workflows/publish-to-ter.yml",
|
||||
"sha256": "62eee6d30da96da6b44d376939f46e823b019cd7e79fff19361eccff90795c4d"
|
||||
},
|
||||
{
|
||||
"path": "skills/typo3-conformance/.github/ISSUE_TEMPLATE/config.yml",
|
||||
"sha256": "8c74d3fce410f9dfcf021dee69ed6a840a64477ba3108baf276ad0d34a264ac7"
|
||||
}
|
||||
],
|
||||
"dirSha256": "2dac5be64a7d70361a08099a0ed7fd471693e4f6b4405d6c25731205d9e9b3ba"
|
||||
},
|
||||
"security": {
|
||||
"scannedAt": null,
|
||||
"scannerVersion": null,
|
||||
"flags": []
|
||||
}
|
||||
}
|
||||
19
skills/agents/.gitignore
vendored
Normal file
19
skills/agents/.gitignore
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Temporary
|
||||
*.tmp
|
||||
*.bak
|
||||
.cache/
|
||||
|
||||
# Test output
|
||||
test-output/
|
||||
*.test.md
|
||||
18
skills/agents/LICENSE
Normal file
18
skills/agents/LICENSE
Normal file
@@ -0,0 +1,18 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 2025 Netresearch DTT GmbH
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
440
skills/agents/README.md
Normal file
440
skills/agents/README.md
Normal file
@@ -0,0 +1,440 @@
|
||||
# AGENTS.md Generator Skill
|
||||
|
||||
Generate and maintain AGENTS.md files following the [public agents.md convention](https://github.com/anthropics/anthropic-sdk-python/blob/main/AGENTS.md).
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Generate AGENTS.md files for current project (thin root + auto-detected scopes)
|
||||
/tmp/agents-skill/scripts/generate-agents.sh .
|
||||
|
||||
# Preview what will be created
|
||||
/tmp/agents-skill/scripts/generate-agents.sh . --dry-run
|
||||
|
||||
# Use verbose root template
|
||||
/tmp/agents-skill/scripts/generate-agents.sh . --style=verbose
|
||||
|
||||
# Validate existing structure
|
||||
/tmp/agents-skill/scripts/validate-structure.sh .
|
||||
```
|
||||
|
||||
## What It Does
|
||||
|
||||
Creates hierarchical AGENTS.md documentation for software projects:
|
||||
|
||||
- **Thin root files** (~30 lines) with precedence rules and global defaults
|
||||
- **Scoped files** for subsystems (backend/, frontend/, internal/, cmd/, etc.)
|
||||
- **Auto-extracted commands** from Makefile, package.json, composer.json, go.mod
|
||||
- **Managed headers** marking files as agent-maintained with timestamps
|
||||
- **Language-specific templates** for Go, PHP, TypeScript, Python
|
||||
- **Idempotent updates** that preserve existing structure
|
||||
|
||||
## Supported Project Types
|
||||
|
||||
### Languages & Frameworks
|
||||
|
||||
- **Go**: Libraries, web apps (Fiber/Echo/Gin), CLI tools (Cobra/urfave/cli)
|
||||
- **PHP**: Composer packages, TYPO3 extensions, Laravel/Symfony apps
|
||||
- **TypeScript/JavaScript**: React, Next.js, Vue, Node.js, Express
|
||||
- **Python**: pip, poetry, pipenv, Django, Flask, FastAPI
|
||||
- **Hybrid**: Multi-language projects (auto-creates scoped files per stack)
|
||||
|
||||
### Auto-Detection
|
||||
|
||||
The skill automatically detects:
|
||||
- Project language and version
|
||||
- Build tools (make, npm, composer, poetry)
|
||||
- Quality tools (linters, formatters, type checkers)
|
||||
- Test frameworks
|
||||
- Framework type (React, TYPO3, Django, etc.)
|
||||
- Directories needing scoped AGENTS.md files
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Generation
|
||||
|
||||
```bash
|
||||
# Generate for current project
|
||||
/tmp/agents-skill/scripts/generate-agents.sh .
|
||||
|
||||
# Generate for specific project
|
||||
/tmp/agents-skill/scripts/generate-agents.sh /path/to/project
|
||||
```
|
||||
|
||||
**Output**:
|
||||
```
|
||||
✅ Created: ./AGENTS.md
|
||||
✅ Created: ./internal/AGENTS.md
|
||||
✅ Created: ./cmd/AGENTS.md
|
||||
✅ Generated: 1 root + 2 scoped files
|
||||
```
|
||||
|
||||
### Template Styles
|
||||
|
||||
#### Thin Root (Default)
|
||||
|
||||
Perfect thin root following simple-ldap-go pattern (~30 lines):
|
||||
|
||||
```bash
|
||||
/tmp/agents-skill/scripts/generate-agents.sh . --style=thin
|
||||
```
|
||||
|
||||
Contains:
|
||||
- Precedence statement
|
||||
- Minimal global rules
|
||||
- Pre-commit checks
|
||||
- Scope index
|
||||
- Conflict resolution
|
||||
|
||||
#### Verbose Root
|
||||
|
||||
Comprehensive root with detailed sections (~100-200 lines):
|
||||
|
||||
```bash
|
||||
/tmp/agents-skill/scripts/generate-agents.sh . --style=verbose
|
||||
```
|
||||
|
||||
Additional sections:
|
||||
- Project overview
|
||||
- Development workflow
|
||||
- Code quality standards
|
||||
- Security guidelines
|
||||
- Testing requirements
|
||||
- Documentation links
|
||||
|
||||
### Options
|
||||
|
||||
```bash
|
||||
--style=thin|verbose Template style (default: thin)
|
||||
--dry-run Preview what will be created without writing files
|
||||
--update Update existing files only (preserve custom content)
|
||||
--force Force regeneration of all files
|
||||
--verbose, -v Verbose output with detection details
|
||||
--help, -h Show help message
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# Preview changes before applying
|
||||
/tmp/agents-skill/scripts/generate-agents.sh . --dry-run
|
||||
|
||||
# Generate verbose root with detailed guidelines
|
||||
/tmp/agents-skill/scripts/generate-agents.sh . --style=verbose
|
||||
|
||||
# Update existing files with refreshed commands and timestamps
|
||||
/tmp/agents-skill/scripts/generate-agents.sh . --update
|
||||
|
||||
# Force regeneration of all files
|
||||
/tmp/agents-skill/scripts/generate-agents.sh . --force
|
||||
|
||||
# Verbose output to see detection process
|
||||
/tmp/agents-skill/scripts/generate-agents.sh . --verbose
|
||||
```
|
||||
|
||||
## Output Structure
|
||||
|
||||
### Thin Root Example
|
||||
|
||||
```markdown
|
||||
<!-- Managed by agent: keep sections and order; edit content, not structure. Last updated: 2025-10-18 -->
|
||||
|
||||
# AGENTS.md (root)
|
||||
|
||||
This file explains repo-wide conventions and where to find scoped rules.
|
||||
**Precedence:** the **closest `AGENTS.md`** to the files you're changing wins. Root holds global defaults only.
|
||||
|
||||
## Global rules
|
||||
- Keep diffs small; add tests for new code paths
|
||||
- Ask first before: adding heavy deps, running full e2e suites, or repo-wide rewrites
|
||||
- Never commit secrets or sensitive data to the repository
|
||||
- Follow Go 1.24 conventions and idioms
|
||||
|
||||
## Minimal pre-commit checks
|
||||
- Typecheck (all packages): `go build -v ./...`
|
||||
- Lint/format (file scope): `gofmt -w <file.go>` and `golangci-lint run ./...`
|
||||
- Unit tests (fast): `go test -v -race -short -timeout=10s ./...`
|
||||
|
||||
## Index of scoped AGENTS.md
|
||||
- `./internal/AGENTS.md` — Backend services (Go)
|
||||
- `./cmd/AGENTS.md` — Command-line interface tools
|
||||
|
||||
## When instructions conflict
|
||||
- The nearest `AGENTS.md` wins. Explicit user prompts override files.
|
||||
- For Go-specific patterns, defer to language idioms and standard library conventions
|
||||
```
|
||||
|
||||
### Scoped File (9-Section Schema)
|
||||
|
||||
Each scoped file follows this structure:
|
||||
|
||||
1. **Overview**: Purpose of this subsystem
|
||||
2. **Setup & environment**: Prerequisites, installation
|
||||
3. **Build & tests**: File-scoped commands (preferred)
|
||||
4. **Code style & conventions**: Language-specific standards
|
||||
5. **Security & safety**: Security practices
|
||||
6. **PR/commit checklist**: Pre-commit requirements
|
||||
7. **Good vs. bad examples**: Concrete code samples
|
||||
8. **When stuck**: Where to find help
|
||||
9. **House Rules** (optional): Overrides of global rules
|
||||
|
||||
## Detection Scripts
|
||||
|
||||
### Project Detection
|
||||
|
||||
```bash
|
||||
/tmp/agents-skill/scripts/detect-project.sh .
|
||||
```
|
||||
|
||||
Returns JSON with detected information:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "go-web-app",
|
||||
"language": "go",
|
||||
"version": "1.24",
|
||||
"build_tool": "make",
|
||||
"framework": "fiber",
|
||||
"has_docker": true,
|
||||
"quality_tools": ["golangci-lint", "gofmt"],
|
||||
"test_framework": "testing",
|
||||
"ci": "github-actions"
|
||||
}
|
||||
```
|
||||
|
||||
### Scope Detection
|
||||
|
||||
```bash
|
||||
/tmp/agents-skill/scripts/detect-scopes.sh .
|
||||
```
|
||||
|
||||
Returns directories needing scoped AGENTS.md:
|
||||
|
||||
```json
|
||||
{
|
||||
"scopes": [
|
||||
{"path": "internal", "type": "backend-go", "files": 15},
|
||||
{"path": "cmd", "type": "cli", "files": 3}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Command Extraction
|
||||
|
||||
```bash
|
||||
/tmp/agents-skill/scripts/extract-commands.sh .
|
||||
```
|
||||
|
||||
Returns auto-extracted build commands:
|
||||
|
||||
```json
|
||||
{
|
||||
"typecheck": "go build -v ./...",
|
||||
"lint": "golangci-lint run ./...",
|
||||
"format": "gofmt -w .",
|
||||
"test": "go test -v -race -short ./...",
|
||||
"build": "go build -v ./...",
|
||||
"dev": ""
|
||||
}
|
||||
```
|
||||
|
||||
## Validation
|
||||
|
||||
```bash
|
||||
/tmp/agents-skill/scripts/validate-structure.sh .
|
||||
```
|
||||
|
||||
Validates:
|
||||
- ✅ Root is thin (≤50 lines or has index)
|
||||
- ✅ All scoped files have 9 sections
|
||||
- ✅ Managed headers present
|
||||
- ✅ Precedence statement in root
|
||||
- ✅ Links from root to scoped files work
|
||||
|
||||
**Example output**:
|
||||
|
||||
```
|
||||
Validating AGENTS.md structure in: .
|
||||
|
||||
=== Root AGENTS.md ===
|
||||
✅ Managed header present: ./AGENTS.md
|
||||
✅ Root is thin: 27 lines
|
||||
✅ Precedence statement present
|
||||
✅ All scope index links work
|
||||
|
||||
=== Scoped AGENTS.md Files ===
|
||||
Checking: internal/AGENTS.md
|
||||
✅ Managed header present: internal/AGENTS.md
|
||||
✅ All required sections present: internal/AGENTS.md
|
||||
|
||||
=== Validation Summary ===
|
||||
✅ All checks passed!
|
||||
```
|
||||
|
||||
## Real-World Examples
|
||||
|
||||
The skill includes real examples from Netresearch projects in `references/examples/`:
|
||||
|
||||
### simple-ldap-go (Perfect Thin Root)
|
||||
|
||||
**26-line root** demonstrating minimal best practice:
|
||||
- Clear precedence statement
|
||||
- File-scoped commands
|
||||
- Scope index with descriptions
|
||||
- No duplication
|
||||
|
||||
See: `references/examples/simple-ldap-go/AGENTS.md`
|
||||
|
||||
### ldap-selfservice (Hybrid Go + TypeScript)
|
||||
|
||||
**Multi-stack project**:
|
||||
- Thin root with navigation
|
||||
- `internal/AGENTS.md` for Go backend
|
||||
- `internal/web/AGENTS.md` for TypeScript + Tailwind frontend
|
||||
|
||||
### t3x-rte_ckeditor_image (TYPO3 Extension)
|
||||
|
||||
**PHP TYPO3 extension**:
|
||||
- Composer-based with Make targets
|
||||
- Scoped for Classes/, Documentation/, Tests/
|
||||
- TYPO3-specific conventions (DI, CGL, PHPStan Level 10)
|
||||
|
||||
### coding_agent_cli (Python CLI)
|
||||
|
||||
**Script-heavy toolset**:
|
||||
- Precedence-focused root
|
||||
- Scoped for scripts/ directory
|
||||
- Python-specific tooling (ruff, mypy, pytest)
|
||||
|
||||
## Managed Headers
|
||||
|
||||
All generated files include a managed header:
|
||||
|
||||
```html
|
||||
<!-- Managed by agent: keep sections and order; edit content, not structure. Last updated: 2025-10-18 -->
|
||||
```
|
||||
|
||||
This indicates:
|
||||
- File is agent-maintained
|
||||
- Section structure should not be changed
|
||||
- Content within sections can be edited
|
||||
- Timestamp tracks last update
|
||||
|
||||
## Idempotent Updates
|
||||
|
||||
Safe to run multiple times:
|
||||
|
||||
1. Checks existing files
|
||||
2. Preserves custom content in sections
|
||||
3. Updates only auto-extracted parts (commands, versions)
|
||||
4. Refreshes timestamps
|
||||
5. Adds missing sections
|
||||
6. No changes if nothing updated
|
||||
|
||||
```bash
|
||||
# Update existing files with refreshed data
|
||||
/tmp/agents-skill/scripts/generate-agents.sh . --update
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Keep Root Thin
|
||||
|
||||
✅ **Good** (simple-ldap-go, 26 lines):
|
||||
- Precedence statement
|
||||
- Minimal global rules
|
||||
- Pre-commit checks
|
||||
- Scope index
|
||||
|
||||
❌ **Bloated** (some projects, 300+ lines):
|
||||
- Detailed setup instructions (→ move to scoped files)
|
||||
- Language-specific patterns (→ move to scoped files)
|
||||
- Extensive examples (→ move to scoped files)
|
||||
|
||||
### Scope Appropriately
|
||||
|
||||
Create scoped files for:
|
||||
- Different technology stacks (backend/, frontend/, api/)
|
||||
- Public vs private packages (internal/, pkg/)
|
||||
- CLI tools (cmd/, cli/)
|
||||
- Utility scripts (scripts/)
|
||||
- Documentation (docs/, examples/)
|
||||
- Testing infrastructure (tests/, testutil/)
|
||||
|
||||
### Auto-Extract Commands
|
||||
|
||||
Don't manually write commands if they exist in:
|
||||
- Makefile targets
|
||||
- package.json scripts
|
||||
- composer.json scripts
|
||||
- CI workflows
|
||||
|
||||
Let the generator extract them automatically.
|
||||
|
||||
## Installation
|
||||
|
||||
### Claude Code Marketplace
|
||||
|
||||
Add to `.claude/marketplace.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "agents",
|
||||
"description": "Generate AGENTS.md files following public convention",
|
||||
"version": "1.0.0",
|
||||
"path": "/tmp/agents-skill"
|
||||
}
|
||||
```
|
||||
|
||||
### Direct Usage
|
||||
|
||||
```bash
|
||||
# Clone skill
|
||||
git clone https://github.com/netresearch/agents-skill.git /tmp/agents-skill
|
||||
|
||||
# Generate for current project
|
||||
/tmp/agents-skill/scripts/generate-agents.sh .
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### No Commands Detected
|
||||
|
||||
```bash
|
||||
# Check what was detected
|
||||
/tmp/agents-skill/scripts/extract-commands.sh . --verbose
|
||||
|
||||
# Fallback: Check Makefile or package.json manually
|
||||
cat Makefile
|
||||
cat package.json
|
||||
```
|
||||
|
||||
### Wrong Project Type
|
||||
|
||||
```bash
|
||||
# Check detection
|
||||
/tmp/agents-skill/scripts/detect-project.sh .
|
||||
|
||||
# Verify files that should be detected
|
||||
ls -la go.mod package.json composer.json pyproject.toml
|
||||
```
|
||||
|
||||
### Scoped File Not Created
|
||||
|
||||
```bash
|
||||
# Check scope detection
|
||||
/tmp/agents-skill/scripts/detect-scopes.sh .
|
||||
|
||||
# Minimum 5 source files needed for scopes (except cmd/, tests/)
|
||||
find internal -name "*.go" | wc -l
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
GPL-2.0-or-later (matching other Netresearch skills)
|
||||
|
||||
## References
|
||||
|
||||
- **Analysis**: `references/analysis.md` - Analysis of 21 real AGENTS.md files
|
||||
- **Examples**: `references/examples/` - Real-world AGENTS.md files from Netresearch projects
|
||||
- **Public Convention**: https://github.com/anthropics/anthropic-sdk-python/blob/main/AGENTS.md
|
||||
390
skills/agents/SKILL.md
Normal file
390
skills/agents/SKILL.md
Normal file
@@ -0,0 +1,390 @@
|
||||
---
|
||||
name: agents
|
||||
version: 1.1.0
|
||||
description: Generate and maintain AGENTS.md files following the public agents.md convention. Use when creating documentation for AI agent workflows, onboarding guides, or when standardizing agent interaction patterns across projects.
|
||||
license: Complete terms in LICENSE.txt
|
||||
---
|
||||
|
||||
# AGENTS.md Generator Skill
|
||||
|
||||
Generate and maintain AGENTS.md files following the public agents.md convention.
|
||||
|
||||
## What This Skill Does
|
||||
|
||||
Creates hierarchical AGENTS.md documentation for software projects:
|
||||
|
||||
- **Thin root files** (~30 lines) with precedence rules and global defaults
|
||||
- **Scoped files** for subsystems (backend/, frontend/, internal/, cmd/, etc.)
|
||||
- **Auto-extracted commands** from Makefile, package.json, composer.json, go.mod
|
||||
- **Managed headers** marking files as agent-maintained with timestamps
|
||||
- **Language-specific templates** for Go, PHP, TypeScript, Python, and hybrid projects
|
||||
- **Idempotent updates** that preserve existing structure
|
||||
|
||||
Based on analysis of 21 real AGENTS.md files across Netresearch projects.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
- **New projects**: Establish baseline AGENTS.md structure
|
||||
- **Existing projects**: Standardize agent documentation
|
||||
- **Team onboarding**: Provide AI assistants with project context
|
||||
- **Multi-repo consistency**: Apply same standards across repositories
|
||||
- **Documentation updates**: Refresh after major changes
|
||||
|
||||
## Usage
|
||||
|
||||
### Generate for Current Project
|
||||
|
||||
```bash
|
||||
# Basic generation (thin root + auto-detected scopes)
|
||||
/tmp/agents-skill/scripts/generate-agents.sh .
|
||||
|
||||
# Dry-run to preview what will be created
|
||||
/tmp/agents-skill/scripts/generate-agents.sh . --dry-run
|
||||
|
||||
# Verbose output with detection details
|
||||
/tmp/agents-skill/scripts/generate-agents.sh . --verbose
|
||||
```
|
||||
|
||||
### Template Styles
|
||||
|
||||
```bash
|
||||
# Thin root (default, ~30 lines, simple-ldap-go style)
|
||||
/tmp/agents-skill/scripts/generate-agents.sh . --style=thin
|
||||
|
||||
# Verbose root (~100-200 lines, ldap-selfservice style)
|
||||
/tmp/agents-skill/scripts/generate-agents.sh . --style=verbose
|
||||
```
|
||||
|
||||
### Update Existing Files
|
||||
|
||||
```bash
|
||||
# Update timestamps and refresh auto-extracted content
|
||||
/tmp/agents-skill/scripts/generate-agents.sh . --update
|
||||
|
||||
# Force regeneration (overwrites existing, keeps structure)
|
||||
/tmp/agents-skill/scripts/generate-agents.sh . --force
|
||||
```
|
||||
|
||||
### Validation
|
||||
|
||||
```bash
|
||||
# Validate existing AGENTS.md structure
|
||||
/tmp/agents-skill/scripts/validate-structure.sh .
|
||||
|
||||
# Check for missing scoped files
|
||||
/tmp/agents-skill/scripts/detect-scopes.sh .
|
||||
```
|
||||
|
||||
## Supported Project Types
|
||||
|
||||
### Languages & Frameworks
|
||||
|
||||
- **Go**: Libraries, web apps (Fiber/Echo/Gin), CLI tools (Cobra/urfave/cli)
|
||||
- **PHP**: Composer packages, TYPO3 extensions, Laravel/Symfony apps
|
||||
- **TypeScript/JavaScript**: React, Next.js, Vue, Node.js, Express
|
||||
- **Python**: pip, poetry, pipenv, Django, Flask, FastAPI
|
||||
- **Hybrid**: Multi-language projects (auto-creates scoped files per stack)
|
||||
|
||||
### Detection Signals
|
||||
|
||||
| Signal | Detection |
|
||||
|--------|-----------|
|
||||
| `go.mod` | Go project, extracts version |
|
||||
| `composer.json` | PHP project, detects TYPO3/Laravel |
|
||||
| `package.json` | Node.js project, detects framework |
|
||||
| `pyproject.toml` | Python project, detects poetry/ruff |
|
||||
| `Makefile` | Extracts targets with `##` comments |
|
||||
| `.github/workflows/` | Extracts CI checks |
|
||||
| `docker-compose.yml` | Docker-first setup |
|
||||
|
||||
## Output Structure
|
||||
|
||||
### Thin Root (Default)
|
||||
|
||||
**~30 lines** following `simple-ldap-go` pattern:
|
||||
|
||||
```markdown
|
||||
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: YYYY-MM-DD -->
|
||||
|
||||
# AGENTS.md (root)
|
||||
|
||||
**Precedence:** The **closest AGENTS.md** to changed files wins. Root holds global defaults only.
|
||||
|
||||
## Global rules
|
||||
- Keep PRs small (~≤300 net LOC)
|
||||
- Conventional Commits: type(scope): subject
|
||||
- Ask before: heavy deps, full e2e, repo rewrites
|
||||
- Never commit secrets or PII
|
||||
|
||||
## Minimal pre-commit checks
|
||||
- Typecheck: [auto-detected from build tools]
|
||||
- Lint: [auto-detected from linters]
|
||||
- Format: [auto-detected from formatters]
|
||||
- Tests: [auto-detected from test runners]
|
||||
|
||||
## Index of scoped AGENTS.md
|
||||
- `./backend/AGENTS.md` — Backend services
|
||||
- `./frontend/AGENTS.md` — Frontend application
|
||||
|
||||
## When instructions conflict
|
||||
Nearest AGENTS.md wins. User prompts override files.
|
||||
```
|
||||
|
||||
### Scoped Files (9-Section Schema)
|
||||
|
||||
Each scoped file follows this structure:
|
||||
|
||||
1. **Overview**: Purpose of this subsystem
|
||||
2. **Setup & environment**: Prerequisites, installation
|
||||
3. **Build & tests**: File-scoped commands (preferred)
|
||||
4. **Code style & conventions**: Language-specific standards
|
||||
5. **Security & safety**: Security practices
|
||||
6. **PR/commit checklist**: Pre-commit requirements
|
||||
7. **Good vs. bad examples**: Concrete code samples
|
||||
8. **When stuck**: Where to find help
|
||||
9. **House Rules** (optional): Overrides of global rules
|
||||
|
||||
### Managed Header
|
||||
|
||||
All generated files include:
|
||||
|
||||
```html
|
||||
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: 2025-10-18 -->
|
||||
```
|
||||
|
||||
This marks files as agent-maintained and provides update tracking.
|
||||
|
||||
## Auto-Detection Features
|
||||
|
||||
### Project Type Detection
|
||||
|
||||
```bash
|
||||
$ /tmp/agents-skill/scripts/detect-project.sh .
|
||||
{
|
||||
"type": "go-web-app",
|
||||
"language": "go",
|
||||
"version": "1.24",
|
||||
"build_tool": "make",
|
||||
"has_docker": true,
|
||||
"quality_tools": ["golangci-lint", "gofmt"],
|
||||
"test_framework": "testing",
|
||||
"ci": "github-actions"
|
||||
}
|
||||
```
|
||||
|
||||
### Scope Detection
|
||||
|
||||
Automatically creates scoped AGENTS.md for directories with ≥5 source files:
|
||||
|
||||
```bash
|
||||
$ /tmp/agents-skill/scripts/detect-scopes.sh .
|
||||
{
|
||||
"scopes": [
|
||||
{"path": "internal", "type": "backend-go", "files": 15},
|
||||
{"path": "cmd", "type": "cli", "files": 3},
|
||||
{"path": "examples", "type": "examples", "files": 8}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Command Extraction
|
||||
|
||||
Extracts actual commands from build tools:
|
||||
|
||||
```bash
|
||||
$ /tmp/agents-skill/scripts/extract-commands.sh .
|
||||
{
|
||||
"typecheck": "go build -v ./...",
|
||||
"lint": "golangci-lint run ./...",
|
||||
"format": "gofmt -w .",
|
||||
"test": "go test -v -race -short ./...",
|
||||
"build": "go build -o bin/app ./cmd/app"
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
Real-world examples from Netresearch projects in `references/examples/`:
|
||||
|
||||
### Go Library (simple-ldap-go)
|
||||
|
||||
**Perfect thin root** (26 lines):
|
||||
- Minimal global rules
|
||||
- File-scoped commands
|
||||
- Clear scope index
|
||||
- No duplication
|
||||
|
||||
### Hybrid App (ldap-selfservice-password-changer)
|
||||
|
||||
**Go backend + TypeScript frontend**:
|
||||
- Root with quick navigation
|
||||
- Scoped: `internal/AGENTS.md` (Go)
|
||||
- Scoped: `internal/web/AGENTS.md` (TypeScript + Tailwind)
|
||||
|
||||
### PHP TYPO3 Extension (t3x-rte_ckeditor_image)
|
||||
|
||||
**Composer-based with Make targets**:
|
||||
- Root with emoji headers
|
||||
- Scoped: `Classes/AGENTS.md` (PHP backend)
|
||||
- Scoped: `Documentation/AGENTS.md` (RST docs)
|
||||
- Scoped: `Tests/AGENTS.md` (PHPUnit tests)
|
||||
|
||||
### Python CLI (coding_agent_cli_toolset)
|
||||
|
||||
**Script-heavy toolset**:
|
||||
- Root with precedence focus
|
||||
- Scoped: `scripts/AGENTS.md`
|
||||
|
||||
## Customization
|
||||
|
||||
### Override Templates
|
||||
|
||||
Copy templates to project and modify:
|
||||
|
||||
```bash
|
||||
cp /tmp/agents-skill/templates/root-thin.md ./.agents-templates/root.md
|
||||
# Edit ./.agents-templates/root.md
|
||||
/tmp/agents-skill/scripts/generate-agents.sh . --template-dir=./.agents-templates
|
||||
```
|
||||
|
||||
### Add Custom Sections
|
||||
|
||||
Templates support placeholders:
|
||||
- `{{PROJECT_NAME}}` - From package.json/composer.json/go.mod
|
||||
- `{{PROJECT_TYPE}}` - Auto-detected type
|
||||
- `{{LANGUAGE}}` - Primary language
|
||||
- `{{BUILD_COMMANDS}}` - Extracted commands
|
||||
- `{{QUALITY_TOOLS}}` - Detected linters/formatters
|
||||
- `{{TIMESTAMP}}` - Current date (YYYY-MM-DD)
|
||||
|
||||
## Idempotent Updates
|
||||
|
||||
Safe to run multiple times:
|
||||
|
||||
1. Checks existing files
|
||||
2. Preserves custom content in sections
|
||||
3. Updates only auto-extracted parts (commands, versions)
|
||||
4. Refreshes timestamps
|
||||
5. Adds missing sections
|
||||
6. No changes if nothing updated
|
||||
|
||||
## Validation
|
||||
|
||||
```bash
|
||||
# Check structure compliance
|
||||
/tmp/agents-skill/scripts/validate-structure.sh .
|
||||
|
||||
# Validates:
|
||||
# ✅ Root is thin (≤50 lines or has index)
|
||||
# ✅ All scoped files have 9 sections
|
||||
# ✅ Managed headers present
|
||||
# ✅ Precedence statement in root
|
||||
# ✅ Links from root to scoped files work
|
||||
# ✅ No duplicate content between root and scoped
|
||||
```
|
||||
|
||||
## Integration with Claude Code
|
||||
|
||||
### As Marketplace Skill
|
||||
|
||||
Add to `claude-code-marketplace`:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "agents",
|
||||
"description": "Generate AGENTS.md files following public convention",
|
||||
"version": "1.0.0",
|
||||
"source": "./skills/agents"
|
||||
}
|
||||
```
|
||||
|
||||
### Direct Usage
|
||||
|
||||
```bash
|
||||
# Clone skill
|
||||
git clone https://github.com/netresearch/agents-skill.git /tmp/agents-skill
|
||||
|
||||
# Generate for current project
|
||||
/tmp/agents-skill/scripts/generate-agents.sh .
|
||||
```
|
||||
|
||||
## Structure Standards Application
|
||||
|
||||
**When creating root AGENTS.md files**, keep them thin (~30 lines):
|
||||
- Include clear precedence statement at top
|
||||
- Define minimal global rules only (PR size, commit format, safety)
|
||||
- List pre-commit checks (typecheck, lint, format, test)
|
||||
- Provide scope index linking to scoped files
|
||||
- Move detailed setup to scoped files (not in root)
|
||||
- Move language-specific patterns to scoped files (not in root)
|
||||
- Move extensive examples to scoped files (not in root)
|
||||
|
||||
**When determining scope boundaries**, create scoped files for:
|
||||
- Different technology stacks: `backend/`, `frontend/`, `api/`
|
||||
- Package visibility: `internal/`, `pkg/` (Go projects)
|
||||
- CLI tools: `cmd/`, `cli/`
|
||||
- Utility scripts: `scripts/`
|
||||
- Documentation and examples: `docs/`, `examples/`
|
||||
- Testing infrastructure: `tests/`, `testutil/`
|
||||
|
||||
**When extracting commands**, automate extraction from:
|
||||
- Makefile targets with `##` comments
|
||||
- package.json scripts section
|
||||
- composer.json scripts section
|
||||
- CI workflow files (.github/workflows/, .gitlab-ci.yml)
|
||||
- Never manually duplicate commands that exist in build tools
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### No Commands Detected
|
||||
|
||||
```bash
|
||||
# Check what was detected
|
||||
/tmp/agents-skill/scripts/extract-commands.sh . --verbose
|
||||
|
||||
# Fallback: Specify commands manually
|
||||
/tmp/agents-skill/scripts/generate-agents.sh . --commands='{"lint":"make lint","test":"make test"}'
|
||||
```
|
||||
|
||||
### Wrong Project Type
|
||||
|
||||
```bash
|
||||
# Override auto-detection
|
||||
/tmp/agents-skill/scripts/generate-agents.sh . --type=go-library
|
||||
|
||||
# Supported types:
|
||||
# go-library, go-web-app, go-cli
|
||||
# php-library, php-typo3, php-laravel
|
||||
# typescript-react, typescript-node
|
||||
# python-library, python-cli
|
||||
# hybrid
|
||||
```
|
||||
|
||||
### Scoped File Not Created
|
||||
|
||||
```bash
|
||||
# Check scope detection
|
||||
/tmp/agents-skill/scripts/detect-scopes.sh .
|
||||
|
||||
# Manually specify scopes
|
||||
/tmp/agents-skill/scripts/generate-agents.sh . --scopes=internal,cmd,examples
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Improvements welcome! Common additions:
|
||||
- New language templates
|
||||
- Better command extraction
|
||||
- Additional validation rules
|
||||
- More real-world examples
|
||||
|
||||
## License
|
||||
|
||||
GPL-2.0-or-later (matching other Netresearch skills)
|
||||
|
||||
## References
|
||||
|
||||
- **Analysis**: `references/analysis.md` - Analysis of 21 real AGENTS.md files
|
||||
- **Prompt**: `references/prompt.md` - Original generation prompt/rule
|
||||
- **Examples**: `references/examples/` - Real-world AGENTS.md files
|
||||
- **Best Practices**: `references/best-practices.md` - Writing guide
|
||||
104
skills/agents/STATUS.md
Normal file
104
skills/agents/STATUS.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# agents-skill Creation Status
|
||||
|
||||
**Created**: 2025-10-18
|
||||
**Status**: ✅ COMPLETE - Fully functional skill ready for use
|
||||
|
||||
## ✅ Completed
|
||||
|
||||
1. **Directory Structure**: Created all necessary directories
|
||||
2. **SKILL.md**: Complete skill metadata and documentation
|
||||
3. **`.gitignore**`: Standard ignores
|
||||
4. **Analysis**: Complete analysis of 21 real AGENTS.md files
|
||||
5. **Templates**: root-thin.md, root-verbose.md, and scoped templates (Go, PHP, TypeScript, CLI)
|
||||
6. **Scripts**: All generator and detection scripts implemented
|
||||
7. **Examples**: Real-world AGENTS.md files from 4 projects copied
|
||||
8. **README.md**: Comprehensive usage guide
|
||||
9. **LICENSE**: GPL-2.0-or-later
|
||||
10. **Git Repository**: Initialized with initial commit
|
||||
|
||||
## 📦 Implementation Summary
|
||||
|
||||
### Templates (✅ Complete)
|
||||
- ✅ `templates/root-thin.md` - Thin root template (simple-ldap-go style, ~30 lines)
|
||||
- ✅ `templates/root-verbose.md` - Verbose root template (~100-200 lines)
|
||||
- ✅ `templates/scoped/backend-go.md` - Go backend 9-section template
|
||||
- ✅ `templates/scoped/backend-php.md` - PHP backend 9-section template
|
||||
- ✅ `templates/scoped/frontend-typescript.md` - TypeScript frontend 9-section template
|
||||
- ✅ `templates/scoped/cli.md` - CLI tools 9-section template
|
||||
|
||||
### Scripts (✅ Complete)
|
||||
- ✅ `scripts/generate-agents.sh` - Main orchestrator with --dry-run, --update, --force, --style
|
||||
- ✅ `scripts/detect-project.sh` - Auto-detect language, version, framework, tools
|
||||
- ✅ `scripts/detect-scopes.sh` - Find directories needing scoped AGENTS.md
|
||||
- ✅ `scripts/extract-commands.sh` - Parse Makefile, package.json, composer.json
|
||||
- ✅ `scripts/validate-structure.sh` - Validate structure compliance
|
||||
- ✅ `scripts/lib/template.sh` - Template rendering helper functions
|
||||
|
||||
### Examples (✅ Complete)
|
||||
- ✅ `references/examples/simple-ldap-go/` - Perfect thin root (26 lines)
|
||||
- ✅ `references/examples/ldap-selfservice/` - Hybrid Go + TypeScript
|
||||
- ✅ `references/examples/t3x-rte-ckeditor-image/` - PHP TYPO3 extension
|
||||
- ✅ `references/examples/coding-agent-cli/` - Python CLI toolset
|
||||
- ✅ `references/analysis.md` - Comprehensive analysis of 21 files
|
||||
|
||||
### Documentation (✅ Complete)
|
||||
- ✅ `README.md` - Comprehensive usage guide with examples
|
||||
- ✅ `SKILL.md` - Complete skill metadata and documentation
|
||||
- ✅ `LICENSE` - GPL-2.0-or-later
|
||||
- ✅ `.gitignore` - Standard ignores
|
||||
|
||||
### Git Repository (✅ Complete)
|
||||
- ✅ Initialized with all files
|
||||
- ✅ Initial commit with descriptive message
|
||||
- ✅ Ready for push to GitHub
|
||||
|
||||
## 🚀 Next Steps
|
||||
|
||||
1. ✅ **Push to GitHub**: Create repository at `https://github.com/netresearch/agents-skill`
|
||||
2. ✅ **Add to marketplace**: Update sync configuration and workflow
|
||||
3. ✅ **Test on real projects**: Validate with simple-ldap-go, t3x-rte_ckeditor_image, etc.
|
||||
|
||||
## 📝 Final Directory Structure
|
||||
|
||||
```
|
||||
/tmp/agents-skill/
|
||||
├── .git/ ✅ Git repository initialized
|
||||
├── .gitignore ✅ Standard ignores
|
||||
├── LICENSE ✅ GPL-2.0-or-later
|
||||
├── README.md ✅ Comprehensive usage guide
|
||||
├── SKILL.md ✅ Complete skill metadata
|
||||
├── STATUS.md ✅ This file
|
||||
├── templates/
|
||||
│ ├── root-thin.md ✅ Thin root template
|
||||
│ ├── root-verbose.md ✅ Verbose root template
|
||||
│ ├── scoped/
|
||||
│ │ ├── backend-go.md ✅ Go backend template
|
||||
│ │ ├── backend-php.md ✅ PHP backend template
|
||||
│ │ ├── cli.md ✅ CLI tools template
|
||||
│ │ └── frontend-typescript.md ✅ TypeScript frontend template
|
||||
│ └── sections/ (future: modular sections)
|
||||
├── scripts/
|
||||
│ ├── detect-project.sh ✅ Project type detection
|
||||
│ ├── detect-scopes.sh ✅ Scope detection
|
||||
│ ├── extract-commands.sh ✅ Build command extraction
|
||||
│ ├── generate-agents.sh ✅ Main generator
|
||||
│ ├── validate-structure.sh ✅ Structure validation
|
||||
│ └── lib/
|
||||
│ └── template.sh ✅ Template rendering helpers
|
||||
└── references/
|
||||
├── analysis.md ✅ Comprehensive analysis
|
||||
└── examples/
|
||||
├── simple-ldap-go/ ✅ Perfect thin root example
|
||||
├── ldap-selfservice/ ✅ Hybrid Go + TypeScript
|
||||
├── t3x-rte-ckeditor-image/ ✅ PHP TYPO3 extension
|
||||
└── coding-agent-cli/ ✅ Python CLI toolset
|
||||
```
|
||||
|
||||
## ✅ Skill is Complete and Ready for Use
|
||||
|
||||
The agents-skill is fully implemented and ready to generate AGENTS.md files for any supported project type (Go, PHP, TypeScript, Python, hybrid).
|
||||
|
||||
**Usage**:
|
||||
```bash
|
||||
/tmp/agents-skill/scripts/generate-agents.sh /path/to/project
|
||||
```
|
||||
45
skills/agents/claudedocs/refactoring-summary.md
Normal file
45
skills/agents/claudedocs/refactoring-summary.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# SKILL.md Refactoring Summary
|
||||
|
||||
**Date:** 2025-11-14
|
||||
**Version Change:** 1.0.0 → 1.1.0
|
||||
**Skill:** agents
|
||||
|
||||
## Changes Applied
|
||||
|
||||
### Pattern 2: Converted "## Best Practices" to Imperative Form
|
||||
- **Before:** "## Best Practices" with subsections containing mixed Do's/Don'ts
|
||||
- **After:** "## Structure Standards Application" with imperative "When X" format
|
||||
|
||||
#### Keep Root Thin
|
||||
- **Before:** "Good" and "Bloated" examples with checkmarks/X marks
|
||||
- **After:** "When creating root AGENTS.md files" with action-oriented instructions
|
||||
- Converted positive examples to directives
|
||||
- Converted negative examples to avoidance instructions
|
||||
|
||||
#### Scope Appropriately
|
||||
- **Before:** "Create scoped files for:" with bullet list
|
||||
- **After:** "When determining scope boundaries" with action-oriented guidance
|
||||
- Same content, imperative presentation
|
||||
|
||||
#### Auto-Extract Commands
|
||||
- **Before:** "Don't manually write commands if they exist in:"
|
||||
- **After:** "When extracting commands, automate extraction from:"
|
||||
- Positive framing with clear directive
|
||||
|
||||
## Impact Analysis
|
||||
|
||||
**Readability:** Improved - clearer action-oriented instructions
|
||||
**Consistency:** Aligned with skill-creator best practices
|
||||
**Usability:** Enhanced - readers know when and how to apply each pattern
|
||||
**Structure:** Maintained all examples while improving presentation
|
||||
|
||||
## Files Modified
|
||||
|
||||
- `/SKILL.md` (lines 1-400)
|
||||
|
||||
## Verification
|
||||
|
||||
- Version number updated in YAML frontmatter: ✓
|
||||
- Best Practices converted to imperative form: ✓
|
||||
- All guidance remains intact: ✓
|
||||
- No broken internal references: ✓
|
||||
21
skills/agents/composer.json
Normal file
21
skills/agents/composer.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "netresearch/agents-skill",
|
||||
"description": "Generate and maintain AGENTS.md files following the public agents.md convention",
|
||||
"type": "ai-agent-skill",
|
||||
"license": "GPL-2.0-or-later",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Netresearch DTT GmbH",
|
||||
"email": "plugins@netresearch.de",
|
||||
"homepage": "https://www.netresearch.de/",
|
||||
"role": "Manufacturer"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"netresearch/composer-agent-skill-plugin": "*"
|
||||
},
|
||||
"keywords": [
|
||||
"ai-agent",
|
||||
"skill"
|
||||
]
|
||||
}
|
||||
320
skills/agents/references/analysis.md
Normal file
320
skills/agents/references/analysis.md
Normal file
@@ -0,0 +1,320 @@
|
||||
# AGENTS.md Analysis Across 6 Netresearch Projects
|
||||
|
||||
## Executive Summary
|
||||
|
||||
**Total AGENTS.md Files Found**: 21 files across 6 projects
|
||||
|
||||
**Patterns Observed**:
|
||||
1. **Root files are thin** (26-348 lines) with precedence rules and global defaults
|
||||
2. **Scoped files are focused** on specific subsystems (backend, frontend, CLI, etc.)
|
||||
3. **Managed header** present in newer files: `<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: YYYY-MM-DD -->`
|
||||
4. **Consistent structure** following the 9-section schema from your prompt
|
||||
|
||||
## File Distribution
|
||||
|
||||
| Project | Root | Scoped Files | Total Lines (root) |
|
||||
|---------|------|--------------|-------------------|
|
||||
| t3x-rte_ckeditor_image | ✅ | Classes/, Documentation/, Resources/, Tests/ | 348 |
|
||||
| coding_agent_cli_toolset | ✅ | scripts/ | 308 |
|
||||
| ldap-selfservice-password-changer | ✅ | internal/, internal/web/ | 282 |
|
||||
| ldap-manager | ✅ | cmd/, internal/, internal/web/, scripts/ | 228 |
|
||||
| raybeam | ✅ | cmd/, internal/ | 209 |
|
||||
| simple-ldap-go | ✅ | docs/, examples/, testutil/ | 26 ⭐ **Perfect thin root** |
|
||||
|
||||
## Key Findings
|
||||
|
||||
### 1. **simple-ldap-go** is the Best Example ⭐
|
||||
|
||||
**Root file** (26 lines):
|
||||
- Minimal global rules
|
||||
- Clear precedence statement
|
||||
- Index of scoped files
|
||||
- No duplication with scoped content
|
||||
|
||||
```markdown
|
||||
## Global rules
|
||||
- Keep diffs small; add tests for new code paths
|
||||
- Ask first before: adding heavy deps, running full e2e suites, or repo-wide rewrites
|
||||
|
||||
## Minimal pre-commit checks
|
||||
- Typecheck (all packages): `go build -v ./...`
|
||||
- Lint/format (file scope): `gofmt -w <file.go>`
|
||||
- Unit tests (fast): `go test -v -race -short -timeout=10s ./...`
|
||||
|
||||
## Index of scoped AGENTS.md
|
||||
- `./examples/AGENTS.md` — Example applications
|
||||
- `./testutil/AGENTS.md` — Testing utilities
|
||||
- `./docs/AGENTS.md` — Documentation
|
||||
```
|
||||
|
||||
### 2. Scoped Files Follow 9-Section Schema
|
||||
|
||||
**Example: simple-ldap-go/examples/AGENTS.md**:
|
||||
1. ✅ Overview
|
||||
2. ✅ Setup & environment
|
||||
3. ✅ Build & tests (file-scoped)
|
||||
4. ✅ Code style & conventions
|
||||
5. ✅ Security & safety
|
||||
6. ✅ PR/commit checklist
|
||||
7. ✅ Good vs. bad examples
|
||||
8. ✅ When stuck
|
||||
9. ⚠️ House Rules (rarely used, only when overriding)
|
||||
|
||||
### 3. Managed Header Usage
|
||||
|
||||
**Present in** (newer projects):
|
||||
- simple-ldap-go (all files)
|
||||
- ldap-selfservice-password-changer (all files)
|
||||
- raybeam (some files)
|
||||
|
||||
**Missing in** (older projects):
|
||||
- t3x-rte_ckeditor_image
|
||||
- coding_agent_cli_toolset
|
||||
|
||||
**Format**:
|
||||
```html
|
||||
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: 2025-10-09 -->
|
||||
```
|
||||
|
||||
### 4. Precedence Rules - Consistent Pattern
|
||||
|
||||
All root files establish precedence clearly:
|
||||
|
||||
**Pattern 1** (verbose):
|
||||
> "This file explains repo-wide conventions and where to find scoped rules. **Precedence:** the **closest `AGENTS.md`** to the files you're changing wins. Root holds global defaults only."
|
||||
|
||||
**Pattern 2** (concise):
|
||||
> "**Precedence**: Nearest AGENTS.md wins. This is the root file with global defaults."
|
||||
|
||||
**Pattern 3** (index-focused):
|
||||
> "## Precedence & Scoped Files
|
||||
> Nearest AGENTS.md wins. Use this root for defaults only."
|
||||
|
||||
### 5. Docker-First vs Native-First
|
||||
|
||||
**Docker-first projects** (ldap-selfservice-password-changer, ldap-manager):
|
||||
```markdown
|
||||
### Setup
|
||||
**Prerequisites**: Docker + Docker Compose (required), Go 1.25+, Node.js 24+, pnpm 10.18+ (for native dev)
|
||||
|
||||
# Docker (recommended)
|
||||
docker compose --profile dev up
|
||||
|
||||
# Native development
|
||||
pnpm install
|
||||
```
|
||||
|
||||
**Native-first projects** (simple-ldap-go, t3x-rte_ckeditor_image):
|
||||
```markdown
|
||||
### Setup
|
||||
**Prerequisites**: Go 1.24, golangci-lint
|
||||
|
||||
# Install
|
||||
go mod download
|
||||
```
|
||||
|
||||
### 6. Language-Specific Patterns
|
||||
|
||||
**Go Projects** (simple-ldap-go, ldap-manager, raybeam):
|
||||
- Minimal pre-commit: `go build -v ./...`, `gofmt -w`, `go test -short`
|
||||
- Go version in global rules (1.24, 1.25)
|
||||
- golangci-lint for comprehensive checks
|
||||
|
||||
**PHP Project** (t3x-rte_ckeditor_image):
|
||||
- Composer scripts for CI pipeline
|
||||
- PHPStan + PHP-CS-Fixer + Rector
|
||||
- Make targets preferred over composer commands
|
||||
|
||||
**Hybrid Projects** (ldap-selfservice-password-changer):
|
||||
- Separate sections for Go backend vs TypeScript frontend
|
||||
- Scoped AGENTS.md for `internal/` (Go) and `internal/web/` (TS)
|
||||
- pnpm for package management (strict version)
|
||||
|
||||
### 7. Quick Start Patterns
|
||||
|
||||
**Best practice** (ldap-selfservice-password-changer):
|
||||
```markdown
|
||||
## Quick Navigation
|
||||
- [internal/AGENTS.md](internal/AGENTS.md) - Go backend services
|
||||
- [internal/web/AGENTS.md](internal/web/AGENTS.md) - TypeScript frontend
|
||||
```
|
||||
|
||||
**Alternative** (simple-ldap-go):
|
||||
```markdown
|
||||
## Index of scoped AGENTS.md
|
||||
- `./examples/AGENTS.md` — Example applications and usage patterns
|
||||
- `./testutil/AGENTS.md` — Testing utilities and container management
|
||||
```
|
||||
|
||||
### 8. House Rules Implementation
|
||||
|
||||
**Global defaults** typically include:
|
||||
- Commits: Conventional Commits, small PRs (~≤300 LOC)
|
||||
- Type-safety: Strict types when supported
|
||||
- SOLID, KISS, DRY, YAGNI principles
|
||||
- SemVer for versioning
|
||||
- No secrets in VCS
|
||||
- Structured logging
|
||||
- WCAG AA for UI projects
|
||||
|
||||
**Scoped overrides** (rare):
|
||||
- Different test coverage targets per module
|
||||
- Module-specific commit conventions
|
||||
- Technology-specific style guides
|
||||
|
||||
### 9. Common Gaps Across Projects
|
||||
|
||||
❌ **Missing .envrc** in most projects (your prompt requires it)
|
||||
❌ **Missing .editorconfig** in some projects
|
||||
❌ **Husky + commitlint** not universally adopted
|
||||
❌ **lint-staged** not implemented in older projects
|
||||
❌ **CI parity section** often missing (should reference GitHub Actions)
|
||||
|
||||
## Recommendations for Your Skill
|
||||
|
||||
### Essential Features
|
||||
|
||||
1. **Template Selection**:
|
||||
- Thin root (simple-ldap-go style) ⭐
|
||||
- Verbose root (ldap-selfservice-password-changer style)
|
||||
- Auto-detect based on project size
|
||||
|
||||
2. **Project Type Detection**:
|
||||
- Go: Look for `go.mod`, detect version from `go.mod` directive
|
||||
- PHP: Look for `composer.json`, detect TYPO3 from dependencies
|
||||
- TypeScript: Look for `tsconfig.json`, detect strict mode
|
||||
- Hybrid: Detect multiple languages, recommend scoped files
|
||||
|
||||
3. **Scoped File Generation**:
|
||||
- **Required scopes**: backend/, frontend/, internal/
|
||||
- **Optional scopes**: cmd/, scripts/, examples/, docs/, testutil/
|
||||
- **Auto-create** if directory exists and has ≥5 files
|
||||
|
||||
4. **Content Extraction**:
|
||||
- **Makefile**: Extract targets with `##` comments → Build & Test Commands
|
||||
- **package.json scripts**: Extract npm/pnpm commands
|
||||
- **go.mod**: Extract Go version → Prerequisites
|
||||
- **composer.json scripts**: Extract PHP quality commands
|
||||
- **GitHub Actions**: Extract CI checks → PR/commit checklist
|
||||
|
||||
5. **Managed Header**:
|
||||
- Always add to new files
|
||||
- Preserve in existing files
|
||||
- Update timestamp on regeneration
|
||||
|
||||
6. **Precedence Rules**:
|
||||
- Auto-add "Nearest AGENTS.md wins" statement
|
||||
- Generate index of scoped files in root
|
||||
- Link from root to scoped files
|
||||
|
||||
### Skill Structure Recommendation
|
||||
|
||||
```
|
||||
agents-skill/
|
||||
├── SKILL.md
|
||||
├── README.md
|
||||
├── templates/
|
||||
│ ├── root-thin.md # simple-ldap-go style (recommended)
|
||||
│ ├── root-verbose.md # ldap-selfservice style
|
||||
│ ├── scoped-backend.md # Go/PHP backend
|
||||
│ ├── scoped-frontend.md # TypeScript/JS frontend
|
||||
│ ├── scoped-cli.md # CLI tools
|
||||
│ ├── scoped-docs.md # Documentation
|
||||
│ ├── scoped-tests.md # Testing utilities
|
||||
│ └── sections/ # Modular sections
|
||||
│ ├── header.md # Managed header template
|
||||
│ ├── precedence.md # Precedence statement
|
||||
│ ├── setup.md # Setup section
|
||||
│ ├── build-commands.md # Build & test commands
|
||||
│ ├── code-style.md # Code style guidelines
|
||||
│ ├── security.md # Security practices
|
||||
│ ├── pr-checklist.md # PR/commit checklist
|
||||
│ ├── examples.md # Good vs bad examples
|
||||
│ └── when-stuck.md # When stuck guidance
|
||||
├── scripts/
|
||||
│ ├── generate-agents.sh # Main generator
|
||||
│ ├── detect-project.sh # Auto-detect project type
|
||||
│ ├── extract-commands.sh # Extract from Makefile/package.json
|
||||
│ └── validate-structure.sh # Validate generated files
|
||||
└── references/
|
||||
├── examples/ # Real-world examples
|
||||
│ ├── go-library.md # simple-ldap-go
|
||||
│ ├── go-web-app.md # ldap-manager
|
||||
│ ├── php-typo3.md # t3x-rte_ckeditor_image
|
||||
│ └── hybrid-app.md # ldap-selfservice-password-changer
|
||||
└── best-practices.md # AGENTS.md writing guide
|
||||
```
|
||||
|
||||
### Key Differentiators
|
||||
|
||||
✅ **Thin root by default** (not verbose like some projects)
|
||||
✅ **Auto-scope detection** (create scoped files when needed)
|
||||
✅ **Command extraction** (don't make user write commands manually)
|
||||
✅ **Managed header** (mark files as agent-maintained)
|
||||
✅ **Language-agnostic** (works with Go, PHP, TypeScript, Python, etc.)
|
||||
✅ **Idempotent** (can be re-run without breaking existing structure)
|
||||
|
||||
## Sample Output Comparison
|
||||
|
||||
### Your Prompt's Expected Output
|
||||
|
||||
**Root AGENTS.md** (following simple-ldap-go pattern):
|
||||
```markdown
|
||||
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: 2025-10-18 -->
|
||||
|
||||
# AGENTS.md (root)
|
||||
|
||||
**Precedence:** The **closest AGENTS.md** to changed files wins. Root holds global defaults only.
|
||||
|
||||
## Global rules
|
||||
- Keep PRs small (~≤300 net LOC)
|
||||
- Conventional Commits: type(scope): subject
|
||||
- Ask before: heavy deps, full e2e, repo rewrites
|
||||
- Never commit secrets or PII
|
||||
|
||||
## Minimal pre-commit checks
|
||||
- Typecheck: [auto-detected command]
|
||||
- Lint: [auto-detected command]
|
||||
- Format: [auto-detected command]
|
||||
- Tests: [auto-detected command]
|
||||
|
||||
## Index of scoped AGENTS.md
|
||||
- `./internal/AGENTS.md` — Go backend services
|
||||
- `./internal/web/AGENTS.md` — TypeScript frontend
|
||||
|
||||
## When instructions conflict
|
||||
Nearest AGENTS.md wins. User prompts override files.
|
||||
```
|
||||
|
||||
**Scoped AGENTS.md** (e.g., internal/AGENTS.md):
|
||||
```markdown
|
||||
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: 2025-10-18 -->
|
||||
|
||||
# AGENTS.md — Backend Services
|
||||
|
||||
## Overview
|
||||
[Auto-generated description of internal/ directory purpose]
|
||||
|
||||
## Setup & environment
|
||||
[Auto-detected from go.mod, .env.example]
|
||||
|
||||
## Build & tests (prefer file-scoped)
|
||||
[Auto-extracted from Makefile, go commands]
|
||||
|
||||
## Code style & conventions
|
||||
[Auto-detected from golangci-lint config, gofmt]
|
||||
|
||||
## Security & safety
|
||||
[Standard Go security practices + project-specific]
|
||||
|
||||
## PR/commit checklist
|
||||
[Auto-extracted from GitHub Actions, Makefile]
|
||||
|
||||
## Good vs. bad examples
|
||||
[Template with placeholders to fill]
|
||||
|
||||
## When stuck
|
||||
- Check root AGENTS.md for global rules
|
||||
- Review sibling modules for patterns
|
||||
```
|
||||
|
||||
308
skills/agents/references/examples/coding-agent-cli/AGENTS.md
Normal file
308
skills/agents/references/examples/coding-agent-cli/AGENTS.md
Normal file
@@ -0,0 +1,308 @@
|
||||
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: 2025-10-09 -->
|
||||
|
||||
# AI CLI Preparation - Agent Guide (Root)
|
||||
|
||||
**Thin root file**: See scoped AGENTS.md files for specific areas.
|
||||
|
||||
## Precedence & Scoped Files
|
||||
|
||||
This root AGENTS.md provides global defaults. **Nearest AGENTS.md wins** for specific rules.
|
||||
|
||||
**Scoped files:**
|
||||
- [scripts/AGENTS.md](scripts/AGENTS.md) - Shell installation scripts
|
||||
|
||||
## Overview
|
||||
|
||||
AI CLI Preparation is an environment audit tool ensuring AI coding agents (like Claude Code) have access to necessary developer tools. It detects 50+ tools, reports versions, and provides installation guidance.
|
||||
|
||||
**Architecture:**
|
||||
- **Phase 1 (Complete)**: Tool detection, version auditing, offline-first caching
|
||||
- **Phase 2 (Planned)**: Context-aware installation/upgrade management (see [docs/PRD.md](docs/PRD.md))
|
||||
|
||||
**Tech Stack:**
|
||||
- Python 3.10+ (standard library only, no external deps for core)
|
||||
- Make for task automation
|
||||
- Shell scripts (Bash) for installation
|
||||
- JSON for caching (latest_versions.json, tools_snapshot.json)
|
||||
|
||||
**Key Files:**
|
||||
- `cli_audit.py` (2,375 lines): Main audit engine, 50+ tool definitions
|
||||
- `smart_column.py`: ANSI/emoji-aware table formatting
|
||||
- `scripts/`: 13+ installation scripts (install/update/uninstall/reconcile)
|
||||
- `docs/`: Comprehensive technical documentation (12 files, 189KB)
|
||||
|
||||
## Setup
|
||||
|
||||
**Requirements:**
|
||||
- Python 3.10+ (Python 3.14.0rc2 tested)
|
||||
- Standard library only (no pip install needed for core)
|
||||
- Optional: pyflakes for linting
|
||||
|
||||
**First-time setup:**
|
||||
```bash
|
||||
# Allow direnv (if using)
|
||||
direnv allow
|
||||
|
||||
# Show available commands
|
||||
make help
|
||||
|
||||
# Update snapshot (requires network)
|
||||
make update
|
||||
|
||||
# Run audit from snapshot (fast, offline-capable)
|
||||
make audit
|
||||
```
|
||||
|
||||
**Environment variables:**
|
||||
```bash
|
||||
# See .env.default for all options
|
||||
CLI_AUDIT_COLLECT=1 # Collect-only mode (write snapshot)
|
||||
CLI_AUDIT_RENDER=1 # Render-only mode (read snapshot)
|
||||
CLI_AUDIT_OFFLINE=1 # Offline mode (manual cache only)
|
||||
CLI_AUDIT_DEBUG=1 # Debug output
|
||||
CLI_AUDIT_JSON=1 # JSON output
|
||||
```
|
||||
|
||||
## Build & Tests
|
||||
|
||||
**Primary commands:**
|
||||
```bash
|
||||
make audit # Render from snapshot (no network, <100ms)
|
||||
make update # Collect fresh data, write snapshot (~10s)
|
||||
make audit-offline # Offline audit with hints
|
||||
make lint # Run pyflakes (if installed)
|
||||
make upgrade # Interactive upgrade guide
|
||||
```
|
||||
|
||||
**Single tool audit:**
|
||||
```bash
|
||||
make audit-ripgrep # Audit specific tool
|
||||
make audit-offline-python-core # Role-based preset
|
||||
```
|
||||
|
||||
**Installation scripts:**
|
||||
```bash
|
||||
make install-python # Install Python toolchain (uv)
|
||||
make install-node # Install Node.js (nvm)
|
||||
make install-core # Install core tools (fd, fzf, ripgrep, jq, etc.)
|
||||
```
|
||||
|
||||
**Testing:**
|
||||
```bash
|
||||
# Smoke test (verifies table output and JSON format)
|
||||
./scripts/test_smoke.sh
|
||||
|
||||
# Test single tool detection
|
||||
CLI_AUDIT_DEBUG=1 python3 cli_audit.py --only ripgrep
|
||||
|
||||
# Validate snapshot
|
||||
jq '.__meta__' tools_snapshot.json
|
||||
```
|
||||
|
||||
**No formal test suite yet** - README acknowledges: "currently ships without tests"
|
||||
- Smoke tests exist (test_smoke.sh)
|
||||
- Manual validation workflows documented
|
||||
|
||||
## Code Style
|
||||
|
||||
**Python:**
|
||||
- PEP 8 style (4-space indent, snake_case)
|
||||
- Type hints used (`from __future__ import annotations`)
|
||||
- Frozen dataclasses for immutability (`@dataclass(frozen=True)`)
|
||||
- Docstrings minimal (focus on inline comments)
|
||||
|
||||
**Formatting:**
|
||||
- EditorConfig enforced: LF, UTF-8, 4 spaces, trim trailing whitespace
|
||||
- No auto-formatter configured (manual formatting)
|
||||
- Lint via pyflakes: `make lint`
|
||||
|
||||
**Shell scripts:**
|
||||
- Bash with `set -euo pipefail`
|
||||
- Shellcheck-compliant (best effort)
|
||||
- Consistent error handling (see scripts/lib/)
|
||||
|
||||
**Conventions:**
|
||||
- File paths: Absolute paths, no auto-commit
|
||||
- Functions: Snake_case, descriptive names
|
||||
- Constants: UPPER_CASE (e.g., MANUAL_LOCK, HINTS_LOCK)
|
||||
- Lock ordering: MANUAL_LOCK → HINTS_LOCK (enforced for safety)
|
||||
|
||||
## Security
|
||||
|
||||
**Secrets:**
|
||||
- No secrets in VCS
|
||||
- GITHUB_TOKEN optional (for GitHub API rate limit increase)
|
||||
- Set via environment: `export GITHUB_TOKEN=ghp_...`
|
||||
|
||||
**Network:**
|
||||
- HTTPS-only for upstream queries
|
||||
- Retry logic with exponential backoff
|
||||
- Per-origin rate limits (GitHub: 5/min, PyPI: 10/min, crates.io: 5/min)
|
||||
- Timeout enforcement (default: 3s, configurable)
|
||||
|
||||
**Input validation:**
|
||||
- Tool names validated against TOOLS registry
|
||||
- Version strings sanitized (extract_version_number)
|
||||
- Subprocess calls use lists, not shell=True (where possible)
|
||||
|
||||
**Caching:**
|
||||
- Atomic file writes prevent corruption
|
||||
- Offline-first design (committed latest_versions.json)
|
||||
- No arbitrary code execution (package manager commands only)
|
||||
|
||||
## PR/Commit Checklist
|
||||
|
||||
**Before commit:**
|
||||
- [ ] Run `make lint` (pyflakes clean)
|
||||
- [ ] Run `make audit` (verify snapshot renders)
|
||||
- [ ] Test affected tool: `make audit-<tool>`
|
||||
- [ ] Update docs if behavior changed (README.md, docs/, scripts/README.md)
|
||||
- [ ] Add/update smoke test if new output format
|
||||
|
||||
**Commit messages:**
|
||||
- Conventional Commits format: `type(scope): description`
|
||||
- Examples:
|
||||
- `feat(audit): add snapshot-based collect/render modes`
|
||||
- `fix(locks): enforce MANUAL_LOCK→HINTS_LOCK ordering`
|
||||
- `docs(prd): add Phase 2 specifications and ADRs`
|
||||
- `chore(cache): update latest_versions.json`
|
||||
|
||||
**Pull requests:**
|
||||
- Keep PRs small (~≤300 net LOC changed)
|
||||
- Link to issue/ticket if exists
|
||||
- Update CHANGELOG section in PR description
|
||||
- Ensure CI passes (when added)
|
||||
|
||||
## Good vs Bad Examples
|
||||
|
||||
**Good: Atomic dataclass with type safety**
|
||||
```python
|
||||
@dataclass(frozen=True)
|
||||
class Tool:
|
||||
name: str
|
||||
candidates: tuple[str, ...]
|
||||
source_kind: str # gh|pypi|crates|npm|gnu|skip
|
||||
source_args: tuple[str, ...]
|
||||
```
|
||||
|
||||
**Bad: Mutable dict with unclear types**
|
||||
```python
|
||||
# Don't do this
|
||||
tool = {
|
||||
'name': 'ripgrep',
|
||||
'candidates': ['rg'], # List instead of tuple
|
||||
'source': 'github', # Unclear allowed values
|
||||
}
|
||||
```
|
||||
|
||||
**Good: Lock ordering enforcement**
|
||||
```python
|
||||
def update_manual_cache(tool: str, version: str) -> None:
|
||||
with MANUAL_LOCK: # Acquire first
|
||||
with HINTS_LOCK: # Then hints
|
||||
# Safe: consistent ordering prevents deadlock
|
||||
```
|
||||
|
||||
**Bad: Lock ordering violation**
|
||||
```python
|
||||
def update_cache(tool: str) -> None:
|
||||
with HINTS_LOCK: # Wrong order!
|
||||
with MANUAL_LOCK:
|
||||
# Deadlock risk
|
||||
```
|
||||
|
||||
**Good: Parallel execution with isolation**
|
||||
```python
|
||||
with ThreadPoolExecutor(max_workers=16) as executor:
|
||||
futures = [executor.submit(audit_tool, tool) for tool in TOOLS]
|
||||
for future in as_completed(futures):
|
||||
result = future.result() # Failures isolated
|
||||
```
|
||||
|
||||
**Bad: Sequential execution**
|
||||
```python
|
||||
results = []
|
||||
for tool in TOOLS: # Slow: 50 tools * 3s = 150s
|
||||
results.append(audit_tool(tool))
|
||||
```
|
||||
|
||||
## When Stuck
|
||||
|
||||
**Tool detection failing:**
|
||||
1. Check PATH: `echo $PATH | tr ':' '\n'`
|
||||
2. Debug single tool: `CLI_AUDIT_DEBUG=1 python3 cli_audit.py --only <tool>`
|
||||
3. Check version flag: `<tool> --version` or `<tool> -v`
|
||||
4. See [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md#version-detection-failures)
|
||||
|
||||
**Network issues:**
|
||||
1. Increase timeout: `CLI_AUDIT_TIMEOUT_SECONDS=10 make update`
|
||||
2. More retries: `CLI_AUDIT_HTTP_RETRIES=5 make update`
|
||||
3. Use offline mode: `make audit-offline`
|
||||
4. See [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md#network-timeout-issues)
|
||||
|
||||
**Cache corruption:**
|
||||
1. Remove caches: `rm latest_versions.json tools_snapshot.json`
|
||||
2. Regenerate: `make update`
|
||||
|
||||
**Installation script fails:**
|
||||
1. Check permissions: `make scripts-perms`
|
||||
2. Debug script: `bash -x ./scripts/install_<tool>.sh`
|
||||
3. See [scripts/README.md](scripts/README.md) for per-script troubleshooting
|
||||
|
||||
**Documentation:**
|
||||
- Start with [docs/QUICK_REFERENCE.md](docs/QUICK_REFERENCE.md) for one-liners
|
||||
- See [docs/INDEX.md](docs/INDEX.md) for navigation by role/task
|
||||
- Architecture details: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)
|
||||
- API reference: [docs/API_REFERENCE.md](docs/API_REFERENCE.md)
|
||||
|
||||
## House Rules
|
||||
|
||||
**Defaults** (override in scoped AGENTS.md if needed):
|
||||
|
||||
**Commits:**
|
||||
- Atomic commits (single logical change)
|
||||
- Conventional Commits: `type(scope): description`
|
||||
- Keep PRs small (~≤300 net LOC changed)
|
||||
- Ticket IDs in commits/PRs if exists
|
||||
|
||||
**Type-safety:**
|
||||
- Use type hints (`from __future__ import annotations`)
|
||||
- Frozen dataclasses for immutability
|
||||
- No `Any` unless truly dynamic
|
||||
|
||||
**Design principles:**
|
||||
- SOLID, KISS, DRY, YAGNI
|
||||
- Composition > Inheritance
|
||||
- Law of Demeter (minimal coupling)
|
||||
|
||||
**Dependencies:**
|
||||
- Standard library preferred (no external Python deps for core)
|
||||
- Latest stable versions when external deps needed
|
||||
- Document why in Decision Log (ADRs for Phase 2)
|
||||
|
||||
**Security:**
|
||||
- No secrets in VCS
|
||||
- HTTPS-only for network calls
|
||||
- No arbitrary code execution
|
||||
- Input validation on external data
|
||||
|
||||
**Documentation currency:**
|
||||
- Update docs in same PR as behavior changes
|
||||
- No drift between code and docs
|
||||
- Document non-obvious decisions in ADRs (see docs/adr/)
|
||||
|
||||
**Testing:**
|
||||
- Aim for ≥80% coverage on changed code (when test suite added)
|
||||
- Bugfixes use TDD: failing test first, then fix
|
||||
- New code paths need tests (future requirement)
|
||||
|
||||
**Current status:**
|
||||
- Phase 1: Production-ready detection/audit
|
||||
- Phase 2: Planned installation/upgrade (see [docs/PRD.md](docs/PRD.md))
|
||||
- No unit tests yet (acknowledged in README)
|
||||
|
||||
---
|
||||
|
||||
**Quick Start:** New to the project? Start with [README.md](README.md) → [docs/QUICK_REFERENCE.md](docs/QUICK_REFERENCE.md) → [docs/INDEX.md](docs/INDEX.md)
|
||||
|
||||
**Contributing:** See [docs/DEVELOPER_GUIDE.md](docs/DEVELOPER_GUIDE.md) for detailed contribution guide
|
||||
@@ -0,0 +1,389 @@
|
||||
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: 2025-10-09 -->
|
||||
|
||||
# Installation Scripts - Agent Guide
|
||||
|
||||
**Scope:** Shell scripts for tool installation, update, uninstall, reconcile
|
||||
|
||||
## Overview
|
||||
|
||||
13+ Bash scripts for installing developer tools with multiple actions:
|
||||
- **install**: Fresh installation (default action)
|
||||
- **update**: Upgrade to latest version
|
||||
- **uninstall**: Remove installation
|
||||
- **reconcile**: Switch to preferred installation method (e.g., system → user)
|
||||
|
||||
**Key scripts:**
|
||||
- `install_core.sh`: Core tools (fd, fzf, ripgrep, jq, yq, bat, delta, just)
|
||||
- `install_python.sh`: Python toolchain via uv
|
||||
- `install_node.sh`: Node.js via nvm
|
||||
- `install_rust.sh`: Rust via rustup
|
||||
- `install_go.sh`, `install_aws.sh`, `install_kubectl.sh`, etc.
|
||||
- `guide.sh`: Interactive upgrade guide
|
||||
- `test_smoke.sh`: Smoke test for audit output
|
||||
|
||||
**Shared utilities:** `lib/` directory (colors, logging, common functions)
|
||||
|
||||
## Setup
|
||||
|
||||
**Requirements:**
|
||||
- Bash 4.0+
|
||||
- `curl` or `wget` for downloads
|
||||
- Internet access for fresh installs
|
||||
- Appropriate permissions (user for `~/.local/bin`, sudo for system)
|
||||
|
||||
**Environment variables:**
|
||||
```bash
|
||||
INSTALL_PREFIX=${INSTALL_PREFIX:-~/.local} # Default: user-level
|
||||
FORCE_INSTALL=1 # Skip confirmation prompts
|
||||
DEBUG=1 # Verbose output
|
||||
```
|
||||
|
||||
**Permissions:**
|
||||
```bash
|
||||
make scripts-perms # Ensure all scripts are executable
|
||||
```
|
||||
|
||||
## Build & Tests
|
||||
|
||||
**Run individual script:**
|
||||
```bash
|
||||
# Install action (default)
|
||||
./scripts/install_python.sh
|
||||
|
||||
# Update action
|
||||
./scripts/install_python.sh update
|
||||
|
||||
# Uninstall action
|
||||
./scripts/install_python.sh uninstall
|
||||
|
||||
# Reconcile action (switch installation method)
|
||||
./scripts/install_node.sh reconcile
|
||||
```
|
||||
|
||||
**Via Make:**
|
||||
```bash
|
||||
make install-python # Install Python toolchain
|
||||
make update-python # Update Python toolchain
|
||||
make uninstall-python # Uninstall Python toolchain
|
||||
make reconcile-node # Switch Node.js to nvm-managed
|
||||
```
|
||||
|
||||
**Smoke test:**
|
||||
```bash
|
||||
./scripts/test_smoke.sh # Verify audit output format
|
||||
```
|
||||
|
||||
**Debug mode:**
|
||||
```bash
|
||||
DEBUG=1 ./scripts/install_python.sh
|
||||
bash -x ./scripts/install_python.sh # Trace execution
|
||||
```
|
||||
|
||||
## Code Style
|
||||
|
||||
**Shell standards:**
|
||||
- Bash 4.0+ features allowed
|
||||
- Shebang: `#!/usr/bin/env bash` or `#!/bin/bash`
|
||||
- Set strict mode: `set -euo pipefail`
|
||||
- `-e`: Exit on error
|
||||
- `-u`: Error on undefined variables
|
||||
- `-o pipefail`: Fail on pipe errors
|
||||
|
||||
**Formatting:**
|
||||
- 4-space indentation (matches EditorConfig)
|
||||
- Function names: lowercase_with_underscores
|
||||
- Constants: UPPER_CASE
|
||||
- Local variables: lowercase
|
||||
|
||||
**Structure:**
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Source shared utilities
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/lib/colors.sh" || true
|
||||
source "${SCRIPT_DIR}/lib/common.sh" || true
|
||||
|
||||
# Main function per action
|
||||
install_tool() {
|
||||
echo_info "Installing <tool>..."
|
||||
# Implementation
|
||||
}
|
||||
|
||||
update_tool() {
|
||||
echo_info "Updating <tool>..."
|
||||
# Implementation
|
||||
}
|
||||
|
||||
uninstall_tool() {
|
||||
echo_info "Uninstalling <tool>..."
|
||||
# Implementation
|
||||
}
|
||||
|
||||
reconcile_tool() {
|
||||
echo_info "Reconciling <tool>..."
|
||||
# Implementation
|
||||
}
|
||||
|
||||
# Action dispatcher
|
||||
ACTION="${1:-install}"
|
||||
case "$ACTION" in
|
||||
install) install_tool ;;
|
||||
update) update_tool ;;
|
||||
uninstall) uninstall_tool ;;
|
||||
reconcile) reconcile_tool ;;
|
||||
*) echo "Usage: $0 {install|update|uninstall|reconcile}"; exit 1 ;;
|
||||
esac
|
||||
```
|
||||
|
||||
**Error handling:**
|
||||
```bash
|
||||
# Good: Check command exists before using
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
echo_error "curl not found. Install it first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Good: Check return codes
|
||||
if ! download_file "$URL" "$DEST"; then
|
||||
echo_error "Download failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Good: Cleanup on error
|
||||
trap 'rm -rf "$TMPDIR"' EXIT ERR
|
||||
```
|
||||
|
||||
**Confirmation prompts:**
|
||||
```bash
|
||||
# Good: Skip prompt if FORCE_INSTALL=1
|
||||
if [[ "${FORCE_INSTALL:-0}" != "1" ]]; then
|
||||
read -p "Install <tool>? [y/N] " -n 1 -r
|
||||
echo
|
||||
[[ ! $REPLY =~ ^[Yy]$ ]] && exit 0
|
||||
fi
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
**Download verification:**
|
||||
```bash
|
||||
# Always use HTTPS
|
||||
URL="https://github.com/owner/repo/releases/download/..."
|
||||
|
||||
# Verify checksums when available
|
||||
EXPECTED_SHA256="abc123..."
|
||||
ACTUAL_SHA256=$(sha256sum "$FILE" | awk '{print $1}')
|
||||
if [[ "$ACTUAL_SHA256" != "$EXPECTED_SHA256" ]]; then
|
||||
echo_error "Checksum mismatch!"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
**Path safety:**
|
||||
```bash
|
||||
# Good: Quote variables, use absolute paths
|
||||
INSTALL_DIR="${HOME}/.local/bin"
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
mv "$TMPFILE" "$INSTALL_DIR/tool"
|
||||
|
||||
# Bad: Unquoted, relative paths
|
||||
mkdir -p $INSTALL_DIR
|
||||
mv tool bin/
|
||||
```
|
||||
|
||||
**Sudo usage:**
|
||||
```bash
|
||||
# Good: Prompt for sudo only when needed
|
||||
if [[ "$INSTALL_PREFIX" == "/usr/local" ]]; then
|
||||
if ! sudo -v; then
|
||||
echo_error "Sudo required for system installation"
|
||||
exit 1
|
||||
fi
|
||||
sudo mv "$FILE" "$INSTALL_PREFIX/bin/"
|
||||
else
|
||||
# User-level, no sudo
|
||||
mv "$FILE" "$INSTALL_PREFIX/bin/"
|
||||
fi
|
||||
```
|
||||
|
||||
**No secrets in scripts:**
|
||||
- No API keys, tokens, passwords in scripts
|
||||
- Use environment variables: `${GITHUB_TOKEN:-}`
|
||||
- Document required env vars in script comments
|
||||
|
||||
## PR/Commit Checklist
|
||||
|
||||
**Before commit:**
|
||||
- [ ] Run `shellcheck <script>` (if available)
|
||||
- [ ] Test install action: `./scripts/install_<tool>.sh`
|
||||
- [ ] Test update action: `./scripts/install_<tool>.sh update`
|
||||
- [ ] Test uninstall action: `./scripts/install_<tool>.sh uninstall`
|
||||
- [ ] Update `scripts/README.md` if new script or behavior change
|
||||
- [ ] Verify script permissions: `make scripts-perms`
|
||||
|
||||
**Script checklist:**
|
||||
- [ ] Shebang: `#!/usr/bin/env bash`
|
||||
- [ ] Strict mode: `set -euo pipefail`
|
||||
- [ ] Source shared lib: `source "${SCRIPT_DIR}/lib/colors.sh"`
|
||||
- [ ] Action dispatcher (install/update/uninstall/reconcile)
|
||||
- [ ] Error handling (check return codes, trap on exit)
|
||||
- [ ] Confirmation prompts (respect FORCE_INSTALL)
|
||||
- [ ] PATH updates (add to ~/.bashrc or ~/.zshrc if needed)
|
||||
|
||||
**Commit messages:**
|
||||
- `feat(scripts): add install_terraform.sh`
|
||||
- `fix(install-python): handle uv bootstrap failure`
|
||||
- `docs(scripts): update README with reconcile action`
|
||||
|
||||
## Good vs Bad Examples
|
||||
|
||||
**Good: Robust download with fallback**
|
||||
```bash
|
||||
download_file() {
|
||||
local url="$1"
|
||||
local dest="$2"
|
||||
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -fsSL "$url" -o "$dest"
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
wget -q "$url" -O "$dest"
|
||||
else
|
||||
echo_error "Neither curl nor wget found"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
```
|
||||
|
||||
**Bad: Assumes curl exists**
|
||||
```bash
|
||||
download_file() {
|
||||
curl -fsSL "$1" -o "$2" # Fails if curl not installed
|
||||
}
|
||||
```
|
||||
|
||||
**Good: Version comparison**
|
||||
```bash
|
||||
version_gt() {
|
||||
test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1"
|
||||
}
|
||||
|
||||
CURRENT_VERSION="1.2.3"
|
||||
LATEST_VERSION="1.3.0"
|
||||
|
||||
if version_gt "$LATEST_VERSION" "$CURRENT_VERSION"; then
|
||||
echo "Upgrade available"
|
||||
fi
|
||||
```
|
||||
|
||||
**Bad: String comparison for versions**
|
||||
```bash
|
||||
if [[ "$LATEST_VERSION" > "$CURRENT_VERSION" ]]; then
|
||||
# Wrong: "1.10.0" < "1.9.0" (string comparison)
|
||||
echo "Upgrade available"
|
||||
fi
|
||||
```
|
||||
|
||||
**Good: Cleanup on exit**
|
||||
```bash
|
||||
TMPDIR=$(mktemp -d)
|
||||
trap 'rm -rf "$TMPDIR"' EXIT ERR
|
||||
|
||||
# Download to temp
|
||||
download_file "$URL" "$TMPDIR/file"
|
||||
# ... process ...
|
||||
# Cleanup happens automatically via trap
|
||||
```
|
||||
|
||||
**Bad: Manual cleanup (error-prone)**
|
||||
```bash
|
||||
TMPDIR=$(mktemp -d)
|
||||
download_file "$URL" "$TMPDIR/file"
|
||||
# ... process ...
|
||||
rm -rf "$TMPDIR" # Skipped if earlier command fails
|
||||
```
|
||||
|
||||
**Good: Action-specific logic**
|
||||
```bash
|
||||
install_rust() {
|
||||
if command -v rustup >/dev/null 2>&1; then
|
||||
echo_warn "rustup already installed"
|
||||
return 0
|
||||
fi
|
||||
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
source "$HOME/.cargo/env"
|
||||
}
|
||||
|
||||
update_rust() {
|
||||
if ! command -v rustup >/dev/null 2>&1; then
|
||||
echo_error "rustup not installed. Run install first."
|
||||
return 1
|
||||
fi
|
||||
|
||||
rustup update
|
||||
}
|
||||
```
|
||||
|
||||
## When Stuck
|
||||
|
||||
**Script fails silently:**
|
||||
1. Add debug: `bash -x ./scripts/install_<tool>.sh`
|
||||
2. Check logs: `./scripts/install_<tool>.sh 2>&1 | tee install.log`
|
||||
3. Verify permissions: `ls -la scripts/`
|
||||
|
||||
**Download fails:**
|
||||
1. Check network: `curl -I https://github.com`
|
||||
2. Check URL: `echo "$URL"` (verify it's correct)
|
||||
3. Try manual download: `curl -fsSL "$URL"`
|
||||
|
||||
**Installation fails:**
|
||||
1. Check prerequisites (e.g., Python for uv, curl for rustup)
|
||||
2. Check disk space: `df -h`
|
||||
3. Check permissions: `ls -ld "$INSTALL_PREFIX"`
|
||||
|
||||
**PATH not updated:**
|
||||
1. Source shell config: `source ~/.bashrc` or `source ~/.zshrc`
|
||||
2. Check PATH: `echo $PATH | tr ':' '\n' | grep local`
|
||||
3. Verify binary location: `ls -la ~/.local/bin/<tool>`
|
||||
|
||||
**Reconcile fails:**
|
||||
1. Check current installation: `which <tool>`
|
||||
2. Check installation method: `cli_audit.py --only <tool>`
|
||||
3. Manually remove old version first: `apt remove <tool>` or `cargo uninstall <tool>`
|
||||
|
||||
**Documentation:**
|
||||
- Script-specific docs: [README.md](README.md) (this directory)
|
||||
- Troubleshooting: [../docs/TROUBLESHOOTING.md](../docs/TROUBLESHOOTING.md)
|
||||
- Architecture: [../docs/DEPLOYMENT.md](../docs/DEPLOYMENT.md#installation-scripts)
|
||||
|
||||
## House Rules
|
||||
|
||||
**Installation preferences** (Phase 2 planning):
|
||||
- User-level preferred: `~/.local/bin` (workstations)
|
||||
- System-level for servers: `/usr/local/bin`
|
||||
- Vendor tools first: rustup, nvm, uv over system packages
|
||||
- See [../docs/adr/ADR-002-package-manager-hierarchy.md](../docs/adr/ADR-002-package-manager-hierarchy.md)
|
||||
|
||||
**Reconciliation strategy:**
|
||||
- Parallel approach: Keep both installations, prefer user via PATH
|
||||
- No automatic removal (user chooses)
|
||||
- See [../docs/adr/ADR-003-parallel-installation-approach.md](../docs/adr/ADR-003-parallel-installation-approach.md)
|
||||
|
||||
**Version policy:**
|
||||
- Always latest by default
|
||||
- Warn on major version upgrades
|
||||
- See [../docs/adr/ADR-004-always-latest-version-policy.md](../docs/adr/ADR-004-always-latest-version-policy.md)
|
||||
|
||||
**Script structure:**
|
||||
- Multi-action support: install, update, uninstall, reconcile
|
||||
- Shared utilities in `lib/`
|
||||
- Consistent error handling and logging
|
||||
- Make integration via `make install-<tool>`
|
||||
|
||||
---
|
||||
|
||||
**Quick Start:** Run `make install-core` to install essential tools, then `make audit` to verify.
|
||||
|
||||
**Troubleshooting:** See [README.md](README.md) for per-script troubleshooting and [../docs/TROUBLESHOOTING.md](../docs/TROUBLESHOOTING.md) for general issues.
|
||||
282
skills/agents/references/examples/ldap-selfservice/AGENTS.md
Normal file
282
skills/agents/references/examples/ldap-selfservice/AGENTS.md
Normal file
@@ -0,0 +1,282 @@
|
||||
# Agent Guidelines
|
||||
|
||||
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: 2025-10-09 -->
|
||||
|
||||
**Precedence**: Nearest AGENTS.md wins. This is the root file with global defaults.
|
||||
|
||||
**Project**: LDAP Selfservice Password Changer — hybrid Go + TypeScript web application with WCAG 2.2 AAA accessibility compliance.
|
||||
|
||||
## Quick Navigation
|
||||
|
||||
- [internal/AGENTS.md](internal/AGENTS.md) - Go backend services
|
||||
- [internal/web/AGENTS.md](internal/web/AGENTS.md) - TypeScript frontend & Tailwind CSS
|
||||
|
||||
## Global Defaults
|
||||
|
||||
### Project Overview
|
||||
|
||||
Self-service password change/reset web app for LDAP/ActiveDirectory with email-based password reset, rate limiting, and strict accessibility standards. Single binary deployment with embedded assets.
|
||||
|
||||
**Stack**: Go 1.25 + Fiber, TypeScript (ultra-strict), Tailwind CSS 4, Docker multi-stage builds, pnpm 10.18
|
||||
|
||||
**Key characteristics**:
|
||||
|
||||
- Docker-first: All dev/CI must work via Docker
|
||||
- Accessibility: WCAG 2.2 AAA compliance (7:1 contrast, keyboard nav, screen readers)
|
||||
- Type-safe: Go with testcontainers, TypeScript with all strict flags
|
||||
- Security-focused: LDAPS, rate limiting, token-based reset, no password storage
|
||||
|
||||
### Setup
|
||||
|
||||
**Prerequisites**: Docker + Docker Compose (required), Go 1.25+, Node.js 24+, pnpm 10.18+ (for native dev)
|
||||
|
||||
```bash
|
||||
# Clone and setup environment
|
||||
git clone <repo>
|
||||
cd ldap-selfservice-password-changer
|
||||
cp .env.local.example .env.local # Edit with your LDAP config
|
||||
|
||||
# Docker (recommended)
|
||||
docker compose --profile dev up
|
||||
|
||||
# Native development
|
||||
pnpm install
|
||||
go mod download
|
||||
pnpm dev # Runs concurrent TS watch, CSS watch, and Go with hot-reload
|
||||
```
|
||||
|
||||
See [docs/development-guide.md](docs/development-guide.md) for comprehensive setup.
|
||||
|
||||
### Build & Test Commands
|
||||
|
||||
**Package manager**: pnpm (specified in package.json: `pnpm@10.18.1`)
|
||||
|
||||
```bash
|
||||
# Build everything
|
||||
pnpm build # Build frontend assets + Go binary
|
||||
|
||||
# Frontend only
|
||||
pnpm build:assets # TypeScript + Tailwind CSS
|
||||
pnpm js:build # TypeScript compile + minify
|
||||
pnpm css:build # Tailwind CSS + PostCSS
|
||||
|
||||
# Development (watch mode)
|
||||
pnpm dev # Concurrent: TS watch, CSS watch, Go hot-reload
|
||||
pnpm js:dev # TypeScript watch
|
||||
pnpm css:dev # CSS watch
|
||||
pnpm go:dev # Go with nodemon hot-reload
|
||||
|
||||
# Tests
|
||||
go test -v ./... # All Go tests with verbose output
|
||||
go test ./internal/... # Specific package tests
|
||||
|
||||
# Formatting
|
||||
pnpm prettier --write . # Format TS, Go templates, config files
|
||||
pnpm prettier --check . # Check formatting (CI)
|
||||
|
||||
# Type checking
|
||||
pnpm js:build # TypeScript strict compilation (no emit in dev)
|
||||
go build -v ./... # Go compilation + type checking
|
||||
```
|
||||
|
||||
**CI commands** (from `.github/workflows/check.yml`):
|
||||
|
||||
- Type check: `pnpm js:build` and `go build -v ./...`
|
||||
- Format check: `pnpm prettier --check .`
|
||||
- Tests: `go test -v ./...`
|
||||
|
||||
### Code Style
|
||||
|
||||
**TypeScript**:
|
||||
|
||||
- Ultra-strict tsconfig: `strict: true`, `noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`, `noPropertyAccessFromIndexSignature`
|
||||
- Prettier formatting: 120 char width, semicolons, double quotes, 2-space tabs
|
||||
- No `any` types - use proper type definitions
|
||||
- Follow existing patterns in `internal/web/static/`
|
||||
|
||||
**Go**:
|
||||
|
||||
- Standard Go formatting (`go fmt`)
|
||||
- Prettier with `prettier-plugin-go-template` for HTML templates
|
||||
- Follow Go project layout: `internal/` for private packages, `main.go` at root
|
||||
- Use testcontainers for integration tests (see `*_test.go` files)
|
||||
- Error wrapping with context
|
||||
|
||||
**General**:
|
||||
|
||||
- Composition over inheritance
|
||||
- SOLID, KISS, DRY, YAGNI principles
|
||||
- Law of Demeter: minimize coupling
|
||||
- No secrets in VCS (use .env.local, excluded from git)
|
||||
|
||||
### Security
|
||||
|
||||
- **No secrets in git**: Use `.env.local` (gitignored), never commit LDAP credentials
|
||||
- **LDAPS required**: Production must use encrypted LDAP connections
|
||||
- **Rate limiting**: 3 requests/hour per IP (configurable via `RATE_LIMIT_*` env vars)
|
||||
- **Token security**: Cryptographic random tokens with configurable expiry
|
||||
- **Input validation**: Strict validation on all user inputs (see `internal/validators/`)
|
||||
- **Dependency scanning**: Renovate enabled, review changelogs for major updates
|
||||
- **No PII logging**: Redact sensitive data in logs
|
||||
- **Run as non-root**: Dockerfile uses UID 65534 (nobody)
|
||||
|
||||
### PR/Commit Checklist
|
||||
|
||||
✅ **Before commit**:
|
||||
|
||||
- [ ] Run `pnpm prettier --write .` (format all)
|
||||
- [ ] Run `pnpm js:build` (TypeScript strict check)
|
||||
- [ ] Run `go test ./...` (all tests pass)
|
||||
- [ ] Run `go build` (compilation check)
|
||||
- [ ] No secrets in changed files
|
||||
- [ ] Update docs if behavior changed
|
||||
- [ ] WCAG 2.2 AAA compliance maintained (if UI changed)
|
||||
|
||||
✅ **Commit format**: Conventional Commits
|
||||
|
||||
```
|
||||
type(scope): description
|
||||
|
||||
Examples:
|
||||
feat(auth): add password reset via email
|
||||
fix(validators): correct password policy regex
|
||||
docs(api): update JSON-RPC examples
|
||||
chore(deps): update pnpm to v10.18.1
|
||||
```
|
||||
|
||||
**No Claude attribution** in commit messages.
|
||||
|
||||
✅ **PR requirements**:
|
||||
|
||||
- [ ] All CI checks pass (types, formatting, tests)
|
||||
- [ ] Keep PRs small (~≤300 net LOC if possible)
|
||||
- [ ] Include ticket ID if applicable: `fix(rate-limit): ISSUE-123: fix memory leak`
|
||||
- [ ] Update relevant docs in same PR (README, AGENTS.md, docs/)
|
||||
|
||||
### Good vs Bad Examples
|
||||
|
||||
**✅ Good - TypeScript strict types**:
|
||||
|
||||
```typescript
|
||||
interface PasswordPolicy {
|
||||
minLength: number;
|
||||
requireNumbers: boolean;
|
||||
}
|
||||
|
||||
function validatePassword(password: string, policy: PasswordPolicy): boolean {
|
||||
const hasNumber = /\d/.test(password);
|
||||
return password.length >= policy.minLength && (!policy.requireNumbers || hasNumber);
|
||||
}
|
||||
```
|
||||
|
||||
**❌ Bad - Using any or unsafe access**:
|
||||
|
||||
```typescript
|
||||
function validatePassword(password: any, policy: any) {
|
||||
// ❌ any types
|
||||
return password.length >= policy.minLength; // ❌ unsafe access
|
||||
}
|
||||
```
|
||||
|
||||
**✅ Good - Go error handling**:
|
||||
|
||||
```go
|
||||
func connectLDAP(config LDAPConfig) (*ldap.Conn, error) {
|
||||
conn, err := ldap.DialURL(config.URL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to LDAP at %s: %w", config.URL, err)
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
```
|
||||
|
||||
**❌ Bad - Ignoring errors**:
|
||||
|
||||
```go
|
||||
func connectLDAP(config LDAPConfig) *ldap.Conn {
|
||||
conn, _ := ldap.DialURL(config.URL) // ❌ ignoring error
|
||||
return conn // ❌ may return nil
|
||||
}
|
||||
```
|
||||
|
||||
**✅ Good - Accessible UI**:
|
||||
|
||||
```html
|
||||
<button type="submit" aria-label="Submit password change" class="bg-blue-600 hover:bg-blue-700 focus:ring-4">
|
||||
Change Password
|
||||
</button>
|
||||
```
|
||||
|
||||
**❌ Bad - Inaccessible UI**:
|
||||
|
||||
```html
|
||||
<div onclick="submit()">Submit</div>
|
||||
❌ not keyboard accessible, wrong semantics
|
||||
```
|
||||
|
||||
### When Stuck
|
||||
|
||||
1. **Check existing docs**: [docs/](docs/) has comprehensive guides
|
||||
2. **Review similar code**: Look for patterns in `internal/` packages
|
||||
3. **Run tests**: `go test -v ./...` often reveals issues
|
||||
4. **Check CI logs**: GitHub Actions shows exact failure points
|
||||
5. **Verify environment**: Ensure `.env.local` is properly configured
|
||||
6. **Docker issues**: `docker compose down -v && docker compose --profile dev up --build`
|
||||
7. **Type errors**: Review `tsconfig.json` strict flags, use proper types
|
||||
8. **Accessibility**: See [docs/accessibility.md](docs/accessibility.md) for WCAG 2.2 AAA guidelines
|
||||
|
||||
### House Rules
|
||||
|
||||
**Docker-First Philosophy**:
|
||||
|
||||
- All dev and CI must work via Docker Compose
|
||||
- Native setup is optional convenience, not requirement
|
||||
- Use profiles in compose.yml: `--profile dev` or `--profile test`
|
||||
|
||||
**Documentation Currency**:
|
||||
|
||||
- Update docs in same PR as code changes
|
||||
- No drift between code and documentation
|
||||
- Keep README, AGENTS.md, and docs/ synchronized
|
||||
|
||||
**Testing Standards**:
|
||||
|
||||
- Aim for ≥80% coverage on changed code
|
||||
- Use testcontainers for integration tests (see existing `*_test.go`)
|
||||
- For bugfixes: write failing test first (TDD)
|
||||
- Tests must pass before PR approval
|
||||
|
||||
**Scope Discipline**:
|
||||
|
||||
- Build only what's requested
|
||||
- No speculative features
|
||||
- MVP first, iterate based on feedback
|
||||
- YAGNI: You Aren't Gonna Need It
|
||||
|
||||
**Accessibility Non-Negotiable**:
|
||||
|
||||
- WCAG 2.2 AAA compliance required
|
||||
- 7:1 contrast ratios for text
|
||||
- Full keyboard navigation support
|
||||
- Screen reader tested (VoiceOver/NVDA)
|
||||
- See [docs/accessibility.md](docs/accessibility.md)
|
||||
|
||||
**Commit Practices**:
|
||||
|
||||
- Atomic commits: one logical change per commit
|
||||
- Conventional Commits format enforced
|
||||
- Never commit secrets or `.env.local`
|
||||
- Keep PRs focused and reviewable
|
||||
|
||||
**Type Safety**:
|
||||
|
||||
- TypeScript: No `any`, all strict flags enabled
|
||||
- Go: Leverage type system, avoid `interface{}`
|
||||
- Validate inputs at boundaries
|
||||
|
||||
**Dependency Management**:
|
||||
|
||||
- Renovate auto-updates enabled
|
||||
- Major version updates require changelog review
|
||||
- Use Context7 MCP or official docs for migrations
|
||||
- Keep pnpm-lock.yaml and go.sum committed
|
||||
@@ -0,0 +1,371 @@
|
||||
# Go Backend Services
|
||||
|
||||
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: 2025-10-09 -->
|
||||
|
||||
**Scope**: Go backend packages in `internal/` directory
|
||||
|
||||
**See also**: [../AGENTS.md](../AGENTS.md) for global standards, [web/AGENTS.md](web/AGENTS.md) for frontend
|
||||
|
||||
## Overview
|
||||
|
||||
Backend services for LDAP selfservice password change/reset functionality. Organized as internal Go packages:
|
||||
|
||||
- **email/**: SMTP email service for password reset tokens
|
||||
- **options/**: Configuration management from environment variables
|
||||
- **ratelimit/**: IP-based rate limiting (3 req/hour default)
|
||||
- **resettoken/**: Cryptographic token generation and validation
|
||||
- **rpc/**: JSON-RPC 2.0 API handlers (password change/reset)
|
||||
- **validators/**: Password policy validation logic
|
||||
- **web/**: HTTP server setup, static assets, routing (see [web/AGENTS.md](web/AGENTS.md))
|
||||
|
||||
## Setup/Environment
|
||||
|
||||
**Required environment variables** (configure in `.env.local`):
|
||||
|
||||
```bash
|
||||
# LDAP connection
|
||||
LDAP_URL=ldaps://ldap.example.com:636
|
||||
LDAP_USER_BASE_DN=ou=users,dc=example,dc=com
|
||||
LDAP_BIND_DN=cn=admin,dc=example,dc=com
|
||||
LDAP_BIND_PASSWORD=secret
|
||||
|
||||
# Email for password reset
|
||||
SMTP_HOST=smtp.example.com
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=noreply@example.com
|
||||
SMTP_PASSWORD=secret
|
||||
SMTP_FROM=noreply@example.com
|
||||
APP_BASE_URL=https://passwd.example.com
|
||||
|
||||
# Rate limiting (optional)
|
||||
RATE_LIMIT_REQUESTS=3
|
||||
RATE_LIMIT_WINDOW=1h
|
||||
|
||||
# Token expiry (optional)
|
||||
TOKEN_EXPIRY_DURATION=1h
|
||||
```
|
||||
|
||||
**Go toolchain**: Requires Go 1.25+ (specified in `go.mod`)
|
||||
|
||||
**Key dependencies**:
|
||||
|
||||
- `github.com/gofiber/fiber/v2` - HTTP server
|
||||
- `github.com/netresearch/simple-ldap-go` - LDAP client
|
||||
- `github.com/testcontainers/testcontainers-go` - Integration testing
|
||||
- `github.com/joho/godotenv` - Environment loading
|
||||
|
||||
## Build & Tests
|
||||
|
||||
```bash
|
||||
# Development
|
||||
go run . # Start server with hot-reload (via pnpm go:dev)
|
||||
go build -v ./... # Compile all packages
|
||||
go test -v ./... # Run all tests with verbose output
|
||||
|
||||
# Specific package testing
|
||||
go test ./internal/validators/... # Test password validators
|
||||
go test ./internal/ratelimit/... # Test rate limiter
|
||||
go test ./internal/resettoken/... # Test token generation
|
||||
go test -run TestSpecificFunction # Run specific test
|
||||
|
||||
# Integration tests (uses testcontainers)
|
||||
go test -v ./internal/email/... # Requires Docker for MailHog container
|
||||
|
||||
# Coverage
|
||||
go test -cover ./... # Coverage summary
|
||||
go test -coverprofile=coverage.out ./... && go tool cover -html=coverage.out
|
||||
|
||||
# Build optimized binary
|
||||
CGO_ENABLED=0 go build -ldflags="-w -s" -o ldap-passwd
|
||||
```
|
||||
|
||||
**CI validation** (from `.github/workflows/check.yml`):
|
||||
|
||||
```bash
|
||||
go mod download
|
||||
go build -v ./...
|
||||
go test -v ./...
|
||||
```
|
||||
|
||||
## Code Style
|
||||
|
||||
**Go Standards**:
|
||||
|
||||
- Use `go fmt` (automatic via Prettier with go-template plugin)
|
||||
- Follow [Effective Go](https://go.dev/doc/effective_go)
|
||||
- Package-level documentation comments required
|
||||
- Exported functions must have doc comments
|
||||
|
||||
**Project Conventions**:
|
||||
|
||||
- Internal packages only: No public API outside this project
|
||||
- Error wrapping with context: `fmt.Errorf("context: %w", err)`
|
||||
- Use structured logging (consider adding in future)
|
||||
- Prefer explicit over implicit
|
||||
- Use interfaces for testability (see `email/service.go`)
|
||||
|
||||
**Naming**:
|
||||
|
||||
- `internal/package/file.go` - implementation
|
||||
- `internal/package/file_test.go` - tests
|
||||
- Descriptive variable names (not `x`, `y`, `tmp`)
|
||||
- No stuttering: `email.Service`, not `email.EmailService`
|
||||
|
||||
**Error Handling**:
|
||||
|
||||
```go
|
||||
// ✅ Good: wrap with context
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect LDAP at %s: %w", config.URL, err)
|
||||
}
|
||||
|
||||
// ❌ Bad: lose context
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ❌ Worse: ignore
|
||||
conn, _ := ldap.Dial(url)
|
||||
```
|
||||
|
||||
**Testing**:
|
||||
|
||||
- Table-driven tests preferred
|
||||
- Use testcontainers for external dependencies (LDAP, SMTP)
|
||||
- Test files colocated with code: `validators/validate_test.go`
|
||||
- Descriptive test names: `TestPasswordValidation_RequiresMinimumLength`
|
||||
|
||||
## Security
|
||||
|
||||
**LDAP Security**:
|
||||
|
||||
- Always use LDAPS in production (`ldaps://` URLs)
|
||||
- Bind credentials in environment, never hardcoded
|
||||
- Validate user input before LDAP queries (prevent injection)
|
||||
- Use `simple-ldap-go` helpers to avoid raw LDAP filter construction
|
||||
|
||||
**Password Security**:
|
||||
|
||||
- Never log passwords (plain or hashed)
|
||||
- No password storage - passwords go directly to LDAP
|
||||
- Passwords only in memory during request lifetime
|
||||
- HTTPS required for transport security
|
||||
|
||||
**Token Security**:
|
||||
|
||||
- Cryptographic random tokens (see `resettoken/token.go`)
|
||||
- Configurable expiry (default 1h)
|
||||
- Single-use tokens (invalidated after use)
|
||||
- No token storage in logs or metrics
|
||||
|
||||
**Rate Limiting**:
|
||||
|
||||
- IP-based limits: 3 requests/hour default
|
||||
- Configurable via `RATE_LIMIT_*` env vars
|
||||
- In-memory store (consider Redis for multi-instance)
|
||||
- Apply to both change and reset endpoints
|
||||
|
||||
**Input Validation**:
|
||||
|
||||
- Strict validation on all user inputs (see `validators/`)
|
||||
- Reject malformed requests early
|
||||
- Validate email format, username format, password policies
|
||||
- No HTML/script injection vectors
|
||||
|
||||
## PR/Commit Checklist
|
||||
|
||||
**Before committing Go code**:
|
||||
|
||||
- [ ] Run `go fmt ./...` (or `pnpm prettier --write .`)
|
||||
- [ ] Run `go vet ./...` (static analysis)
|
||||
- [ ] Run `go test ./...` (all tests pass)
|
||||
- [ ] Run `go build` (compilation check)
|
||||
- [ ] Update package doc comments if API changed
|
||||
- [ ] Add/update tests for new functionality
|
||||
- [ ] Check for sensitive data in logs
|
||||
- [ ] Verify error messages provide useful context
|
||||
|
||||
**Testing requirements**:
|
||||
|
||||
- New features must have tests
|
||||
- Bug fixes must have regression tests
|
||||
- Aim for ≥80% coverage on changed packages
|
||||
- Integration tests for external dependencies
|
||||
|
||||
**Documentation**:
|
||||
|
||||
- Update package doc comments (godoc)
|
||||
- Update [docs/api-reference.md](../docs/api-reference.md) for RPC changes
|
||||
- Update [docs/development-guide.md](../docs/development-guide.md) for new setup steps
|
||||
- Update environment variable examples in `.env` and docs
|
||||
|
||||
## Good vs Bad Examples
|
||||
|
||||
**✅ Good: Type-safe configuration**
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
LDAPURL string `env:"LDAP_URL" validate:"required,url"`
|
||||
BindDN string `env:"LDAP_BIND_DN" validate:"required"`
|
||||
BindPassword string `env:"LDAP_BIND_PASSWORD" validate:"required"`
|
||||
}
|
||||
|
||||
func LoadConfig() (*Config, error) {
|
||||
var cfg Config
|
||||
if err := env.Parse(&cfg); err != nil {
|
||||
return nil, fmt.Errorf("parse config: %w", err)
|
||||
}
|
||||
return &cfg, nil
|
||||
}
|
||||
```
|
||||
|
||||
**❌ Bad: Unsafe configuration**
|
||||
|
||||
```go
|
||||
func LoadConfig() *Config {
|
||||
return &Config{
|
||||
LDAPURL: os.Getenv("LDAP_URL"), // ❌ no validation, may be empty
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**✅ Good: Table-driven tests**
|
||||
|
||||
```go
|
||||
func TestPasswordValidation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
password string
|
||||
policy PasswordPolicy
|
||||
wantErr bool
|
||||
}{
|
||||
{"valid password", "Test123!", PasswordPolicy{MinLength: 8}, false},
|
||||
{"too short", "Ab1!", PasswordPolicy{MinLength: 8}, true},
|
||||
{"no numbers", "TestTest", PasswordPolicy{RequireNumbers: true}, true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := ValidatePassword(tt.password, tt.policy)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("got error %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**❌ Bad: Non-descriptive tests**
|
||||
|
||||
```go
|
||||
func TestPassword(t *testing.T) {
|
||||
err := ValidatePassword("test") // ❌ what policy? what's expected?
|
||||
if err == nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**✅ Good: Interface for testability**
|
||||
|
||||
```go
|
||||
type EmailService interface {
|
||||
SendResetToken(ctx context.Context, to, token string) error
|
||||
}
|
||||
|
||||
type SMTPService struct {
|
||||
host string
|
||||
port int
|
||||
}
|
||||
|
||||
func (s *SMTPService) SendResetToken(ctx context.Context, to, token string) error {
|
||||
// real implementation
|
||||
}
|
||||
|
||||
// In tests, use mock implementation
|
||||
type MockEmailService struct {
|
||||
SendFunc func(ctx context.Context, to, token string) error
|
||||
}
|
||||
```
|
||||
|
||||
**❌ Bad: Hard-to-test concrete dependency**
|
||||
|
||||
```go
|
||||
func ResetPassword(username string) error {
|
||||
service := NewSMTPService() // ❌ hardcoded, can't mock
|
||||
return service.SendEmail(...)
|
||||
}
|
||||
```
|
||||
|
||||
## When Stuck
|
||||
|
||||
**Go-specific issues**:
|
||||
|
||||
1. **Module issues**: `go mod tidy` to clean dependencies
|
||||
2. **Import errors**: Check `go.mod` requires correct versions
|
||||
3. **Test failures**: `go test -v ./... -run FailingTest` for verbose output
|
||||
4. **LDAP connection**: Verify `LDAP_URL` format and network access
|
||||
5. **Email testing**: Ensure Docker running for testcontainers (MailHog)
|
||||
6. **Rate limit testing**: Tests may fail if system time incorrect
|
||||
|
||||
**Debugging**:
|
||||
|
||||
```bash
|
||||
# Verbose test output
|
||||
go test -v ./internal/package/...
|
||||
|
||||
# Run specific test
|
||||
go test -run TestName ./internal/package/
|
||||
|
||||
# Race detector (for concurrency issues)
|
||||
go test -race ./...
|
||||
|
||||
# Build with debug info
|
||||
go build -gcflags="all=-N -l"
|
||||
```
|
||||
|
||||
**Common pitfalls**:
|
||||
|
||||
- **Nil pointer dereference**: Check error returns before using values
|
||||
- **Context cancellation**: Always respect `context.Context` in long operations
|
||||
- **Resource leaks**: Defer `Close()` calls immediately after acquiring resources
|
||||
- **Goroutine leaks**: Ensure all goroutines can exit
|
||||
- **Time zones**: Use `time.UTC` for consistency
|
||||
|
||||
## Package-Specific Notes
|
||||
|
||||
### email/
|
||||
|
||||
- Uses testcontainers for integration tests
|
||||
- MailHog container spins up automatically in tests
|
||||
- Mock `EmailService` interface for unit tests in other packages
|
||||
|
||||
### options/
|
||||
|
||||
- Configuration loaded from environment via `godotenv`
|
||||
- Validation happens at startup (fail-fast)
|
||||
- See `.env.local.example` for required variables
|
||||
|
||||
### ratelimit/
|
||||
|
||||
- In-memory store (map with mutex)
|
||||
- Consider Redis for multi-instance deployments
|
||||
- Tests use fixed time.Now for deterministic results
|
||||
|
||||
### resettoken/
|
||||
|
||||
- Crypto/rand for token generation (never math/rand)
|
||||
- Base64 URL encoding (safe for URLs)
|
||||
- Store tokens server-side with expiry
|
||||
|
||||
### rpc/
|
||||
|
||||
- JSON-RPC 2.0 specification compliance
|
||||
- Error codes defined in [docs/api-reference.md](../docs/api-reference.md)
|
||||
- Request validation before processing
|
||||
|
||||
### validators/
|
||||
|
||||
- Pure functions (no side effects)
|
||||
- Configurable policies from environment
|
||||
- Clear error messages for user feedback
|
||||
@@ -0,0 +1,448 @@
|
||||
# Frontend - TypeScript & Tailwind CSS
|
||||
|
||||
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: 2025-10-09 -->
|
||||
|
||||
**Scope**: Frontend assets in `internal/web/` directory - TypeScript, Tailwind CSS, HTML templates
|
||||
|
||||
**See also**: [../../AGENTS.md](../../AGENTS.md) for global standards, [../AGENTS.md](../AGENTS.md) for Go backend
|
||||
|
||||
## Overview
|
||||
|
||||
Frontend implementation for LDAP selfservice password changer with strict accessibility compliance:
|
||||
|
||||
- **static/**: Client-side TypeScript, compiled CSS, static assets
|
||||
- **js/**: TypeScript source files (compiled to ES modules)
|
||||
- **styles.css**: Tailwind CSS output
|
||||
- Icons, logos, favicons, manifest
|
||||
- **templates/**: Go HTML templates (\*.gohtml)
|
||||
- **handlers.go**: HTTP route handlers
|
||||
- **middleware.go**: Security headers, CORS, etc.
|
||||
- **server.go**: Fiber server setup
|
||||
|
||||
**Key characteristics**:
|
||||
|
||||
- **WCAG 2.2 AAA**: 7:1 contrast, keyboard navigation, screen reader support, adaptive density
|
||||
- **Ultra-strict TypeScript**: All strict flags enabled, no `any` types
|
||||
- **Tailwind CSS 4**: Utility-first, dark mode, responsive, accessible patterns
|
||||
- **Progressive enhancement**: Works without JavaScript (forms submit via HTTP)
|
||||
- **Password manager friendly**: Proper autocomplete attributes
|
||||
|
||||
## Setup/Environment
|
||||
|
||||
**Prerequisites**: Node.js 24+, pnpm 10.18+ (from root `package.json`)
|
||||
|
||||
```bash
|
||||
# From project root
|
||||
pnpm install # Install dependencies
|
||||
|
||||
# Development (watch mode)
|
||||
pnpm css:dev # Tailwind CSS watch
|
||||
pnpm js:dev # TypeScript watch
|
||||
# OR
|
||||
pnpm dev # Concurrent: CSS + TS + Go hot-reload
|
||||
```
|
||||
|
||||
**No .env needed for frontend** - all config comes from Go backend
|
||||
|
||||
**Browser targets**: Modern browsers with ES module support (Chrome 90+, Firefox 88+, Safari 14+, Edge 90+)
|
||||
|
||||
## Build & Tests
|
||||
|
||||
```bash
|
||||
# Build frontend assets
|
||||
pnpm build:assets # TypeScript + CSS (production builds)
|
||||
|
||||
# TypeScript
|
||||
pnpm js:build # Compile TS → ES modules + minify
|
||||
pnpm js:dev # Watch mode with preserveWatchOutput
|
||||
tsc --noEmit # Type check only (no output)
|
||||
|
||||
# CSS
|
||||
pnpm css:build # Tailwind + PostCSS → styles.css
|
||||
pnpm css:dev # Watch mode
|
||||
|
||||
# Formatting
|
||||
pnpm prettier --write internal/web/ # Format TS, CSS, HTML templates
|
||||
pnpm prettier --check internal/web/ # Check formatting (CI)
|
||||
```
|
||||
|
||||
**No unit tests yet** - TypeScript strict mode catches most errors, integration via Go tests
|
||||
|
||||
**CI validation** (from `.github/workflows/check.yml`):
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm js:build # TypeScript strict compilation
|
||||
pnpm prettier --check .
|
||||
```
|
||||
|
||||
**Accessibility testing**:
|
||||
|
||||
- Keyboard navigation: Tab through all interactive elements
|
||||
- Screen reader: Test with VoiceOver (macOS/iOS) or NVDA (Windows)
|
||||
- Contrast: Verify 7:1 ratios with browser dev tools
|
||||
- See [../../docs/accessibility.md](../../docs/accessibility.md) for comprehensive guide
|
||||
|
||||
## Code Style
|
||||
|
||||
**TypeScript Ultra-Strict** (from `tsconfig.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
"strict": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true
|
||||
}
|
||||
```
|
||||
|
||||
**No `any` types allowed**:
|
||||
|
||||
```typescript
|
||||
// ✅ Good: explicit types
|
||||
function validatePassword(password: string, minLength: number): boolean {
|
||||
return password.length >= minLength;
|
||||
}
|
||||
|
||||
// ❌ Bad: any type
|
||||
function validatePassword(password: any): boolean {
|
||||
return password.length >= 8; // ❌ unsafe
|
||||
}
|
||||
```
|
||||
|
||||
**Prettier formatting**:
|
||||
|
||||
- 120 char width
|
||||
- 2-space indentation
|
||||
- Semicolons required
|
||||
- Double quotes (not single)
|
||||
- Trailing comma: none
|
||||
|
||||
**File organization**:
|
||||
|
||||
- TypeScript source: `static/js/*.ts`
|
||||
- Output: `static/js/*.js` (minified ES modules)
|
||||
- CSS input: `tailwind.css` (Tailwind directives)
|
||||
- CSS output: `static/styles.css` (PostCSS processed)
|
||||
|
||||
## Accessibility Standards (WCAG 2.2 AAA)
|
||||
|
||||
**Required compliance** - not optional:
|
||||
|
||||
### Keyboard Navigation
|
||||
|
||||
- All interactive elements focusable with Tab
|
||||
- Visual focus indicators (4px outline, 7:1 contrast)
|
||||
- Logical tab order (top to bottom, left to right)
|
||||
- No keyboard traps
|
||||
- Skip links where needed
|
||||
|
||||
### Screen Readers
|
||||
|
||||
- Semantic HTML: `<button>`, `<input>`, `<label>`, not `<div onclick>`
|
||||
- ARIA labels on icon-only buttons: `aria-label="Submit"`
|
||||
- Error messages: `aria-describedby` linking to error text
|
||||
- Live regions for dynamic content: `aria-live="polite"`
|
||||
- Form field associations: `<label for="id">` + `<input id="id">`
|
||||
|
||||
### Color & Contrast
|
||||
|
||||
- Text: 7:1 contrast ratio (AAA)
|
||||
- Large text (18pt+): 4.5:1 minimum
|
||||
- Focus indicators: 3:1 against adjacent colors
|
||||
- Dark mode: same contrast requirements
|
||||
- Never rely on color alone (use icons, text, patterns)
|
||||
|
||||
### Responsive & Adaptive
|
||||
|
||||
- Responsive: layout adapts to viewport size
|
||||
- Text zoom: 200% without horizontal scroll
|
||||
- Adaptive density: spacing adjusts for user preferences
|
||||
- Touch targets: 44×44 CSS pixels minimum (mobile)
|
||||
|
||||
### Examples
|
||||
|
||||
**✅ Good: Accessible button**
|
||||
|
||||
```html
|
||||
<button type="submit" class="btn-primary focus:ring-4 focus:ring-blue-300" aria-label="Submit password change">
|
||||
<svg aria-hidden="true">...</svg>
|
||||
Change Password
|
||||
</button>
|
||||
```
|
||||
|
||||
**❌ Bad: Inaccessible div-button**
|
||||
|
||||
```html
|
||||
<div onclick="submit()" class="button">❌ not keyboard accessible Submit</div>
|
||||
```
|
||||
|
||||
**✅ Good: Form with error handling**
|
||||
|
||||
```html
|
||||
<form>
|
||||
<label for="password">New Password</label>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
aria-describedby="password-error"
|
||||
aria-invalid="true"
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
<div id="password-error" role="alert">Password must be at least 8 characters</div>
|
||||
</form>
|
||||
```
|
||||
|
||||
**❌ Bad: Form without associations**
|
||||
|
||||
```html
|
||||
<form>
|
||||
<div>Password</div>
|
||||
❌ not a label, no association <input type="password" /> ❌ no autocomplete, no error linkage
|
||||
<div style="color: red">Error</div>
|
||||
❌ no role="alert", only color
|
||||
</form>
|
||||
```
|
||||
|
||||
## Tailwind CSS Patterns
|
||||
|
||||
**Use utility classes**, not custom CSS:
|
||||
|
||||
**✅ Good: Utility classes**
|
||||
|
||||
```html
|
||||
<button
|
||||
class="rounded-lg bg-blue-600 px-4 py-2 font-semibold text-white hover:bg-blue-700 focus:ring-4 focus:ring-blue-300"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
```
|
||||
|
||||
**❌ Bad: Custom CSS**
|
||||
|
||||
```html
|
||||
<button class="custom-button">Submit</button>
|
||||
<style>
|
||||
.custom-button {
|
||||
background: blue;
|
||||
} /* ❌ Use Tailwind utilities */
|
||||
</style>
|
||||
```
|
||||
|
||||
**Dark mode support**:
|
||||
|
||||
```html
|
||||
<div class="bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">Content</div>
|
||||
```
|
||||
|
||||
**Responsive design**:
|
||||
|
||||
```html
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
<!-- Responsive grid: 1 col mobile, 2 tablet, 3 desktop -->
|
||||
</div>
|
||||
```
|
||||
|
||||
**Focus states (required)**:
|
||||
|
||||
```html
|
||||
<button class="focus:ring-4 focus:ring-blue-300 focus:outline-none">
|
||||
<!-- 4px focus ring, 7:1 contrast -->
|
||||
</button>
|
||||
```
|
||||
|
||||
## TypeScript Patterns
|
||||
|
||||
**Strict null checking**:
|
||||
|
||||
```typescript
|
||||
// ✅ Good: handle nulls explicitly
|
||||
function getElement(id: string): HTMLElement | null {
|
||||
return document.getElementById(id);
|
||||
}
|
||||
|
||||
const el = getElement("password");
|
||||
if (el) {
|
||||
// ✅ null check
|
||||
el.textContent = "Hello";
|
||||
}
|
||||
|
||||
// ❌ Bad: assume non-null
|
||||
const el = getElement("password");
|
||||
el.textContent = "Hello"; // ❌ may crash if null
|
||||
```
|
||||
|
||||
**Type guards**:
|
||||
|
||||
```typescript
|
||||
// ✅ Good: type guard for forms
|
||||
function isHTMLFormElement(element: Element): element is HTMLFormElement {
|
||||
return element instanceof HTMLFormElement;
|
||||
}
|
||||
|
||||
const form = document.querySelector("form");
|
||||
if (form && isHTMLFormElement(form)) {
|
||||
form.addEventListener("submit", handleSubmit);
|
||||
}
|
||||
```
|
||||
|
||||
**No unsafe array access**:
|
||||
|
||||
```typescript
|
||||
// ✅ Good: check array bounds
|
||||
const items = ["a", "b", "c"];
|
||||
const first = items[0]; // string | undefined (noUncheckedIndexedAccess)
|
||||
if (first) {
|
||||
console.log(first.toUpperCase());
|
||||
}
|
||||
|
||||
// ❌ Bad: unsafe access
|
||||
console.log(items[0].toUpperCase()); // ❌ may crash if empty array
|
||||
```
|
||||
|
||||
## PR/Commit Checklist
|
||||
|
||||
**Before committing frontend code**:
|
||||
|
||||
- [ ] Run `pnpm js:build` (TypeScript strict check)
|
||||
- [ ] Run `pnpm prettier --write internal/web/`
|
||||
- [ ] Verify keyboard navigation works
|
||||
- [ ] Test with screen reader (VoiceOver/NVDA)
|
||||
- [ ] Check contrast ratios (7:1 for text)
|
||||
- [ ] Test dark mode
|
||||
- [ ] Verify password manager autofill works
|
||||
- [ ] No console errors in browser
|
||||
- [ ] Test on mobile viewport (responsive)
|
||||
|
||||
**Accessibility checklist**:
|
||||
|
||||
- [ ] All interactive elements keyboard accessible
|
||||
- [ ] Focus indicators visible (4px outline, 7:1 contrast)
|
||||
- [ ] ARIA labels on icon-only buttons
|
||||
- [ ] Form fields properly labeled
|
||||
- [ ] Error messages linked with aria-describedby
|
||||
- [ ] No color-only information conveyance
|
||||
- [ ] Touch targets ≥44×44 CSS pixels (mobile)
|
||||
|
||||
**Performance checklist**:
|
||||
|
||||
- [ ] Minified JS (via `pnpm js:minify`)
|
||||
- [ ] CSS optimized (cssnano via PostCSS)
|
||||
- [ ] No unused Tailwind classes (purged automatically)
|
||||
- [ ] No console.log in production code
|
||||
|
||||
## Good vs Bad Examples
|
||||
|
||||
**✅ Good: Type-safe DOM access**
|
||||
|
||||
```typescript
|
||||
function setupPasswordToggle(): void {
|
||||
const toggle = document.getElementById("toggle-password");
|
||||
const input = document.getElementById("password");
|
||||
|
||||
if (!toggle || !(input instanceof HTMLInputElement)) {
|
||||
return; // Guard against missing elements
|
||||
}
|
||||
|
||||
toggle.addEventListener("click", () => {
|
||||
input.type = input.type === "password" ? "text" : "password";
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**❌ Bad: Unsafe DOM access**
|
||||
|
||||
```typescript
|
||||
function setupPasswordToggle() {
|
||||
const toggle = document.getElementById("toggle-password")!; // ❌ non-null assertion
|
||||
const input = document.getElementById("password") as any; // ❌ any type
|
||||
|
||||
toggle.addEventListener("click", () => {
|
||||
input.type = input.type === "password" ? "text" : "password"; // ❌ may crash
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**✅ Good: Accessible form validation**
|
||||
|
||||
```typescript
|
||||
function showError(input: HTMLInputElement, message: string): void {
|
||||
const errorId = `${input.id}-error`;
|
||||
let errorEl = document.getElementById(errorId);
|
||||
|
||||
if (!errorEl) {
|
||||
errorEl = document.createElement("div");
|
||||
errorEl.id = errorId;
|
||||
errorEl.setAttribute("role", "alert");
|
||||
errorEl.className = "text-red-600 dark:text-red-400 text-sm mt-1";
|
||||
input.parentElement?.appendChild(errorEl);
|
||||
}
|
||||
|
||||
errorEl.textContent = message;
|
||||
input.setAttribute("aria-invalid", "true");
|
||||
input.setAttribute("aria-describedby", errorId);
|
||||
}
|
||||
```
|
||||
|
||||
**❌ Bad: Inaccessible validation**
|
||||
|
||||
```typescript
|
||||
function showError(input: any, message: string) {
|
||||
// ❌ any type
|
||||
input.style.borderColor = "red"; // ❌ color only, no text
|
||||
alert(message); // ❌ blocks UI, not persistent
|
||||
}
|
||||
```
|
||||
|
||||
## When Stuck
|
||||
|
||||
**TypeScript issues**:
|
||||
|
||||
1. **Type errors**: Check `tsconfig.json` flags, use proper types (no `any`)
|
||||
2. **Null errors**: Add null checks or type guards
|
||||
3. **Module errors**: Ensure ES module syntax (`import`/`export`)
|
||||
4. **Build errors**: `pnpm install` to refresh dependencies
|
||||
|
||||
**CSS issues**:
|
||||
|
||||
1. **Styles not applying**: Check Tailwind purge config, rebuild with `pnpm css:build`
|
||||
2. **Dark mode broken**: Use `dark:` prefix on utilities
|
||||
3. **Responsive broken**: Use `md:`, `lg:` breakpoint prefixes
|
||||
4. **Custom classes**: Don't - use Tailwind utilities instead
|
||||
|
||||
**Accessibility issues**:
|
||||
|
||||
1. **Keyboard nav broken**: Check tab order, focus indicators
|
||||
2. **Screen reader confusion**: Verify ARIA labels, semantic HTML
|
||||
3. **Contrast failure**: Use darker colors, test with dev tools
|
||||
4. **See**: [../../docs/accessibility.md](../../docs/accessibility.md)
|
||||
|
||||
**Browser dev tools**:
|
||||
|
||||
- Accessibility tab: Check ARIA, contrast, structure
|
||||
- Lighthouse: Run accessibility audit (aim for 100 score)
|
||||
- Console: No errors in production code
|
||||
|
||||
## Testing Workflow
|
||||
|
||||
**Manual testing required** (no automated frontend tests yet):
|
||||
|
||||
1. **Visual testing**: Check all pages in light/dark mode
|
||||
2. **Keyboard testing**: Tab through all interactive elements
|
||||
3. **Screen reader testing**: Use VoiceOver (Cmd+F5) or NVDA
|
||||
4. **Responsive testing**: Test mobile, tablet, desktop viewports
|
||||
5. **Browser testing**: Chrome, Firefox, Safari, Edge
|
||||
6. **Password manager**: Test autofill with 1Password, LastPass, etc.
|
||||
|
||||
**Accessibility testing tools**:
|
||||
|
||||
- Browser dev tools Lighthouse
|
||||
- axe DevTools extension
|
||||
- WAVE browser extension
|
||||
- Manual keyboard/screen reader testing (required)
|
||||
|
||||
**Integration testing**: Go backend tests exercise full request/response flow including frontend templates
|
||||
27
skills/agents/references/examples/simple-ldap-go/AGENTS.md
Normal file
27
skills/agents/references/examples/simple-ldap-go/AGENTS.md
Normal file
@@ -0,0 +1,27 @@
|
||||
<!-- Managed by agent: keep sections and order; edit content, not structure. Last updated: 2025-09-29 -->
|
||||
|
||||
# AGENTS.md (root)
|
||||
|
||||
This file explains repo-wide conventions and where to find scoped rules.
|
||||
**Precedence:** the **closest `AGENTS.md`** to the files you're changing wins. Root holds global defaults only.
|
||||
|
||||
## Global rules
|
||||
- Keep diffs small; add tests for new code paths
|
||||
- Ask first before: adding heavy deps, running full e2e suites, or repo-wide rewrites
|
||||
- Never commit secrets or sensitive data to the repository
|
||||
- Follow Go 1.24 conventions and idioms
|
||||
- Maintain minimum test coverage of 40%
|
||||
|
||||
## Minimal pre-commit checks
|
||||
- Typecheck (all packages): `go build -v ./...`
|
||||
- Lint/format (file scope): `gofmt -w <file.go>` and `~/go/bin/golangci-lint run ./...`
|
||||
- Unit tests (fast): `go test -v -race -short -timeout=10s ./...`
|
||||
|
||||
## Index of scoped AGENTS.md
|
||||
- `./examples/AGENTS.md` — Example applications and usage patterns
|
||||
- `./testutil/AGENTS.md` — Testing utilities and container management
|
||||
- `./docs/AGENTS.md` — Documentation and guides
|
||||
|
||||
## When instructions conflict
|
||||
- The nearest `AGENTS.md` wins. Explicit user prompts override files.
|
||||
- For Go-specific patterns, defer to language idioms and standard library conventions
|
||||
@@ -0,0 +1,45 @@
|
||||
<!-- Managed by agent: keep sections and order; edit content, not structure. Last updated: 2025-09-29 -->
|
||||
|
||||
# AGENTS.md — Examples
|
||||
|
||||
## Overview
|
||||
Example applications demonstrating library usage patterns for authentication, user management, performance optimization, context handling, and error patterns. Entry points are the main.go files in each subdirectory.
|
||||
|
||||
## Setup & environment
|
||||
- Install: `go mod download`
|
||||
- Run example: `go run examples/<name>/main.go`
|
||||
- Env: Examples use environment variables from `.env` files when present
|
||||
|
||||
## Build & tests (prefer file-scoped)
|
||||
- Typecheck a file: `go build -v examples/<name>/main.go`
|
||||
- Format a file: `gofmt -w examples/<name>/main.go`
|
||||
- Run example: `go run examples/<name>/main.go`
|
||||
|
||||
## Code style & conventions
|
||||
- Examples should be self-contained and runnable
|
||||
- Use clear variable names that explain the concept
|
||||
- Include comments explaining non-obvious patterns
|
||||
- Error handling should demonstrate best practices
|
||||
- Keep examples focused on a single concept
|
||||
|
||||
## Security & safety
|
||||
- Never include real credentials in examples
|
||||
- Use placeholder values like "ldap.example.com"
|
||||
- Document required permissions clearly
|
||||
- Examples should fail gracefully without real LDAP server
|
||||
|
||||
## PR/commit checklist
|
||||
- Examples must compile without errors
|
||||
- Include README.md explaining the example's purpose
|
||||
- Test example with both real and mock LDAP servers if possible
|
||||
- Ensure examples follow library best practices
|
||||
|
||||
## Good vs. bad examples
|
||||
- Good: `authentication/main.go` (clear flow, error handling)
|
||||
- Good: `context-usage/main.go` (proper context propagation)
|
||||
- Pattern to follow: Simple, focused, well-commented demonstrations
|
||||
|
||||
## When stuck
|
||||
- Check the main library documentation in ../docs/
|
||||
- Review similar examples in sibling directories
|
||||
- Ensure you have the latest library version
|
||||
@@ -0,0 +1,348 @@
|
||||
# AI Agent Development Guide
|
||||
|
||||
**Project:** rte_ckeditor_image - TYPO3 CKEditor 5 Image Extension
|
||||
**Type:** TYPO3 CMS Extension (PHP 8.2+ + JavaScript/ES6)
|
||||
**License:** GPL-2.0-or-later
|
||||
|
||||
## 📋 Documentation Structure
|
||||
|
||||
This project uses a three-tier documentation system:
|
||||
|
||||
- **[claudedocs/](claudedocs/)** - AI session context (Markdown, gitignored, temporary)
|
||||
- **[Documentation/](Documentation/)** - Official TYPO3 docs (RST, published, permanent)
|
||||
- **Root** - Project essentials (README, CONTRIBUTING, SECURITY, LICENSE)
|
||||
|
||||
See **[claudedocs/INDEX.md](claudedocs/INDEX.md)** for AI context navigation and **[Documentation/AGENTS.md](Documentation/AGENTS.md)** for TYPO3 documentation system guide.
|
||||
|
||||
## 🎯 Quick Start
|
||||
|
||||
```bash
|
||||
# First time setup
|
||||
composer install
|
||||
make help # See all available targets
|
||||
|
||||
# Development workflow
|
||||
make lint # Run all linters
|
||||
make format # Fix code style
|
||||
make test # Run tests
|
||||
make ci # Full CI check (pre-commit)
|
||||
|
||||
# Composer shortcuts (if make unavailable)
|
||||
composer ci:test:php:lint # PHP syntax check
|
||||
composer ci:test:php:phpstan # Static analysis
|
||||
composer ci:test:php:cgl # Code style check
|
||||
composer ci:cgl # Fix code style
|
||||
```
|
||||
|
||||
## 🏗️ Setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- **PHP:** 8.2-8.9 with extensions: dom, libxml
|
||||
- **Composer:** Latest stable
|
||||
- **TYPO3:** 13.4+ (cms-core, cms-backend, cms-frontend, cms-rte-ckeditor)
|
||||
- **direnv:** Optional but recommended
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
# Clone and install
|
||||
git clone https://github.com/netresearch/t3x-rte_ckeditor_image.git
|
||||
cd t3x-rte_ckeditor_image
|
||||
composer install
|
||||
|
||||
# Enable direnv (optional)
|
||||
direnv allow
|
||||
```
|
||||
|
||||
## 🔧 Build & Test Commands
|
||||
|
||||
### Fast Quality Checks (Pre-commit)
|
||||
|
||||
```bash
|
||||
make lint # PHP lint + PHPStan + style check
|
||||
make format # Auto-fix code style
|
||||
make typecheck # PHPStan static analysis
|
||||
```
|
||||
|
||||
### Full CI Suite
|
||||
|
||||
```bash
|
||||
make ci # Complete CI pipeline
|
||||
make test # All tests (when available)
|
||||
```
|
||||
|
||||
### Individual Commands
|
||||
|
||||
```bash
|
||||
# PHP Linting
|
||||
composer ci:test:php:lint
|
||||
|
||||
# Static Analysis
|
||||
composer ci:test:php:phpstan
|
||||
|
||||
# Code Style Check
|
||||
composer ci:test:php:cgl
|
||||
|
||||
# Code Style Fix
|
||||
composer ci:cgl
|
||||
|
||||
# Rector (PHP Modernization)
|
||||
composer ci:test:php:rector
|
||||
composer ci:rector # Apply changes
|
||||
```
|
||||
|
||||
## 📝 Code Style
|
||||
|
||||
### PHP Standards
|
||||
|
||||
- **Base:** PSR-12 + PER-CS 2.0
|
||||
- **Strict types:** Required in all files (`declare(strict_types=1);`)
|
||||
- **Header comments:** Auto-managed by PHP-CS-Fixer
|
||||
- **Config:** `Build/.php-cs-fixer.dist.php`
|
||||
|
||||
### Key Rules
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the package netresearch/rte-ckeditor-image.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Netresearch\RteCKEditorImage\Controller;
|
||||
|
||||
// Imports: Classes, constants, functions
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
// Alignment on = and =>
|
||||
$config = [
|
||||
'short' => 'value',
|
||||
'longer' => 'another',
|
||||
];
|
||||
```
|
||||
|
||||
### JavaScript Standards
|
||||
|
||||
- **ES6 modules:** CKEditor 5 plugin format
|
||||
- **No npm tooling:** TYPO3-managed assets
|
||||
- **Style:** Follow CKEditor 5 conventions
|
||||
- **Location:** `Resources/Public/JavaScript/`
|
||||
|
||||
## 🔒 Security
|
||||
|
||||
- **No secrets in VCS:** Use TYPO3's environment configuration
|
||||
- **Dependency scanning:** Renovate enabled (see `renovate.json`)
|
||||
- **Static analysis:** PHPStan with strict rules
|
||||
- **TYPO3 security:** Follow TYPO3 Security Guidelines
|
||||
- **File uploads:** Use FAL (File Abstraction Layer)
|
||||
- **XSS prevention:** All output escaped via Fluid templates
|
||||
|
||||
## ✅ PR/Commit Checklist
|
||||
|
||||
Before committing:
|
||||
|
||||
1. ✅ **Lint passed:** `make lint` or `composer ci:test:php:lint`
|
||||
2. ✅ **Style fixed:** `make format` or `composer ci:cgl`
|
||||
3. ✅ **Static analysis:** `composer ci:test:php:phpstan` (no new errors)
|
||||
4. ✅ **Rector check:** `composer ci:test:php:rector` (no suggestions)
|
||||
5. ✅ **Docs updated:** Update relevant docs/ files if API changed
|
||||
6. ✅ **CHANGELOG:** Add entry if user-facing change
|
||||
7. ✅ **Conventional Commits:** Use format: `type(scope): message`
|
||||
8. ✅ **Small PRs:** Keep ≤300 net LOC changed
|
||||
|
||||
### Commit Format
|
||||
|
||||
```
|
||||
<type>(<scope>): <subject>
|
||||
|
||||
[optional body]
|
||||
|
||||
[optional footer]
|
||||
```
|
||||
|
||||
**Types:** `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`
|
||||
**Scopes:** `backend`, `frontend`, `config`, `docs`, `build`
|
||||
|
||||
**Examples:**
|
||||
```
|
||||
feat(backend): add image processing hook for WebP
|
||||
fix(frontend): resolve style drop-down disabled for typo3image
|
||||
docs(api): update DataHandling API reference
|
||||
```
|
||||
|
||||
## 🎓 Good vs Bad Examples
|
||||
|
||||
### ✅ Good: TYPO3 Pattern
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Netresearch\RteCKEditorImage\Controller;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||
|
||||
final class SelectImageController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ResourceFactory $resourceFactory
|
||||
) {
|
||||
}
|
||||
|
||||
public function infoAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$fileUid = (int)($request->getQueryParams()['fileId'] ?? 0);
|
||||
// Implementation...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ❌ Bad: Missing strict types, no DI
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Netresearch\RteCKEditorImage\Controller;
|
||||
|
||||
class SelectImageController
|
||||
{
|
||||
public function infoAction($request)
|
||||
{
|
||||
$fileUid = $_GET['fileId']; // Direct superglobal access
|
||||
$factory = new ResourceFactory(); // No DI
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ✅ Good: CKEditor 5 Plugin
|
||||
|
||||
```javascript
|
||||
export default class Typo3Image extends Core.Plugin {
|
||||
static get requires() {
|
||||
return ['StyleUtils', 'GeneralHtmlSupport'];
|
||||
}
|
||||
|
||||
init() {
|
||||
const editor = this.editor;
|
||||
this.listenTo(styleUtils, 'isStyleEnabledForBlock', (event, [style]) => {
|
||||
// Event handling...
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ❌ Bad: Missing dependencies
|
||||
|
||||
```javascript
|
||||
class Typo3Image extends Core.Plugin {
|
||||
// Missing requires() - breaks style integration
|
||||
init() {
|
||||
// Implementation...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🆘 When Stuck
|
||||
|
||||
1. **AI Context:** Start with [claudedocs/INDEX.md](claudedocs/INDEX.md) for project navigation
|
||||
2. **Architecture:** Review [claudedocs/ARCHITECTURE.md](claudedocs/ARCHITECTURE.md)
|
||||
3. **Security:** Check [claudedocs/SECURITY.md](claudedocs/SECURITY.md)
|
||||
4. **API Reference:** See [claudedocs/API_REFERENCE.md](claudedocs/API_REFERENCE.md)
|
||||
5. **Development Guide:** Follow [claudedocs/DEVELOPMENT_GUIDE.md](claudedocs/DEVELOPMENT_GUIDE.md)
|
||||
6. **TYPO3 Docs Guide:** Read [Documentation/AGENTS.md](Documentation/AGENTS.md)
|
||||
7. **Published Manual:** https://docs.typo3.org/p/netresearch/rte-ckeditor-image/main/en-us/
|
||||
8. **TYPO3 Core Docs:** https://docs.typo3.org/
|
||||
9. **Issues:** https://github.com/netresearch/t3x-rte_ckeditor_image/issues
|
||||
|
||||
### Common Issues
|
||||
|
||||
- **Style drop-down disabled:** Missing `GeneralHtmlSupport` dependency (v13.0.0+)
|
||||
- **Images not in frontend:** Missing static template include
|
||||
- **PHPStan errors:** Run `composer ci:test:php:phpstan:baseline` to update baseline
|
||||
- **Code style fails:** Run `composer ci:cgl` to auto-fix
|
||||
|
||||
## 📐 House Rules
|
||||
|
||||
### Commits & PRs
|
||||
|
||||
- **Atomic commits:** One logical change per commit
|
||||
- **Conventional Commits:** Required format (see checklist)
|
||||
- **Small PRs:** Target ≤300 net LOC changed
|
||||
- **Branch naming:** `feature/short-description`, `fix/issue-123`
|
||||
|
||||
### Design Principles
|
||||
|
||||
- **SOLID:** Single responsibility, Open/closed, Liskov, Interface segregation, Dependency inversion
|
||||
- **KISS:** Keep it simple, stupid
|
||||
- **DRY:** Don't repeat yourself
|
||||
- **YAGNI:** You aren't gonna need it
|
||||
- **Composition > Inheritance:** Prefer composition
|
||||
- **Law of Demeter:** Minimize coupling
|
||||
|
||||
### Dependencies
|
||||
|
||||
- **Latest stable:** Use current TYPO3 13.4+ versions
|
||||
- **Renovate:** Auto-updates enabled
|
||||
- **Major updates:** Require changelog review + migration notes
|
||||
- **Composer:** Lock file committed
|
||||
|
||||
### API & Versioning
|
||||
|
||||
- **SemVer:** Semantic versioning (MAJOR.MINOR.PATCH)
|
||||
- **TYPO3 compatibility:** Follow TYPO3 versioning
|
||||
- **Breaking changes:** Increment major version
|
||||
- **Deprecations:** Add `@deprecated` tag + removal plan
|
||||
|
||||
### Testing
|
||||
|
||||
- **TYPO3 Testing Framework:** Use `typo3/testing-framework`
|
||||
- **Functional tests:** For database/integration scenarios
|
||||
- **Unit tests:** For isolated logic
|
||||
- **Test location:** `Tests/Functional/`, `Tests/Unit/`
|
||||
|
||||
### Licensing
|
||||
|
||||
- **License:** AGPL-3.0-or-later
|
||||
- **SPDX:** Use SPDX identifiers
|
||||
- **Headers:** Auto-managed by PHP-CS-Fixer
|
||||
- **Third-party:** Document in CHANGELOG
|
||||
|
||||
## 🔗 Related Files
|
||||
|
||||
**Root Documentation:**
|
||||
- **[README.md](README.md)** - Project overview and quick links
|
||||
- **[CONTRIBUTING.md](CONTRIBUTING.md)** - Contribution guidelines
|
||||
- **[SECURITY.md](SECURITY.md)** - Security policy
|
||||
- **[LICENSE](LICENSE)** - GPL-2.0-or-later license
|
||||
|
||||
**AI Session Context (gitignored):**
|
||||
- **[claudedocs/INDEX.md](claudedocs/INDEX.md)** - Navigation hub
|
||||
- **[claudedocs/PROJECT_OVERVIEW.md](claudedocs/PROJECT_OVERVIEW.md)** - Project summary
|
||||
- **[claudedocs/ARCHITECTURE.md](claudedocs/ARCHITECTURE.md)** - System design
|
||||
- **[claudedocs/DEVELOPMENT_GUIDE.md](claudedocs/DEVELOPMENT_GUIDE.md)** - Development workflow
|
||||
- **[claudedocs/API_REFERENCE.md](claudedocs/API_REFERENCE.md)** - PHP API docs
|
||||
- **[claudedocs/SECURITY.md](claudedocs/SECURITY.md)** - Security analysis
|
||||
|
||||
**Official TYPO3 Documentation:**
|
||||
- **[Documentation/](Documentation/)** - RST documentation (published)
|
||||
- **[Documentation/AGENTS.md](Documentation/AGENTS.md)** - TYPO3 docs system guide
|
||||
|
||||
**Configuration:**
|
||||
- **[composer.json](composer.json)** - Dependencies & scripts
|
||||
- **[Build/](Build/)** - Development tools configuration
|
||||
|
||||
## 📚 Additional Resources
|
||||
|
||||
- **Repository:** https://github.com/netresearch/t3x-rte_ckeditor_image
|
||||
- **Packagist:** https://packagist.org/packages/netresearch/rte-ckeditor-image
|
||||
- **TYPO3 Ext:** https://extensions.typo3.org/extension/rte_ckeditor_image
|
||||
- **TYPO3 Docs:** https://docs.typo3.org/
|
||||
- **CKEditor 5:** https://ckeditor.com/docs/ckeditor5/
|
||||
@@ -0,0 +1,392 @@
|
||||
# Classes/AGENTS.md
|
||||
|
||||
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: 2025-10-15 -->
|
||||
|
||||
**Scope:** PHP backend components (Controllers, EventListeners, DataHandling, Utils)
|
||||
**Parent:** [../AGENTS.md](../AGENTS.md)
|
||||
|
||||
## 📋 Overview
|
||||
|
||||
PHP backend implementation for TYPO3 CKEditor Image extension. Components:
|
||||
|
||||
### Controllers
|
||||
- **SelectImageController** - Image browser wizard, file selection, image info API
|
||||
- **ImageRenderingController** - Image rendering and processing for frontend
|
||||
- **ImageLinkRenderingController** - Link-wrapped image rendering
|
||||
|
||||
### EventListeners
|
||||
- **RteConfigurationListener** - PSR-14 event for RTE configuration injection
|
||||
|
||||
### DataHandling
|
||||
- **RteImagesDbHook** - Database hooks for image magic reference handling
|
||||
- **RteImageSoftReferenceParser** - Soft reference parsing for RTE images
|
||||
|
||||
### Backend Components
|
||||
- **RteImagePreviewRenderer** - Backend preview rendering
|
||||
|
||||
### Utilities
|
||||
- **ProcessedFilesHandler** - File processing and manipulation utilities
|
||||
|
||||
## 🏗️ Architecture Patterns
|
||||
|
||||
### TYPO3 Patterns
|
||||
- **FAL (File Abstraction Layer):** All file operations via ResourceFactory
|
||||
- **PSR-7 Request/Response:** HTTP message interfaces for controllers
|
||||
- **PSR-14 Events:** Event-driven configuration and hooks
|
||||
- **Dependency Injection:** Constructor-based DI (TYPO3 v13+)
|
||||
- **Service Configuration:** `Configuration/Services.yaml` for DI registration
|
||||
|
||||
### File Structure
|
||||
```
|
||||
Classes/
|
||||
├── Backend/
|
||||
│ └── Preview/
|
||||
│ └── RteImagePreviewRenderer.php
|
||||
├── Controller/
|
||||
│ ├── ImageLinkRenderingController.php
|
||||
│ ├── ImageRenderingController.php
|
||||
│ └── SelectImageController.php
|
||||
├── DataHandling/
|
||||
│ └── SoftReference/
|
||||
│ └── RteImageSoftReferenceParser.php
|
||||
├── Database/
|
||||
│ └── RteImagesDbHook.php
|
||||
├── EventListener/
|
||||
│ └── RteConfigurationListener.php
|
||||
└── Utils/
|
||||
└── ProcessedFilesHandler.php
|
||||
```
|
||||
|
||||
## 🔧 Build & Tests
|
||||
|
||||
```bash
|
||||
# PHP-specific quality checks
|
||||
make lint # All linters (syntax + PHPStan + Rector + style)
|
||||
composer ci:test:php:lint # PHP syntax check
|
||||
composer ci:test:php:phpstan # Static analysis
|
||||
composer ci:test:php:rector # Rector modernization check
|
||||
composer ci:test:php:cgl # Code style check
|
||||
|
||||
# Fixes
|
||||
make format # Auto-fix code style
|
||||
composer ci:cgl # Alternative: fix style
|
||||
composer ci:rector # Apply Rector changes
|
||||
|
||||
# Full CI
|
||||
make ci # Complete pipeline
|
||||
```
|
||||
|
||||
## 📝 Code Style
|
||||
|
||||
### Required Patterns
|
||||
|
||||
**1. Strict Types (Always First)**
|
||||
```php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
```
|
||||
|
||||
**2. File Header (Auto-managed by PHP-CS-Fixer)**
|
||||
```php
|
||||
/**
|
||||
* This file is part of the package netresearch/rte-ckeditor-image.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
```
|
||||
|
||||
**3. Import Order**
|
||||
- Classes first
|
||||
- Functions second
|
||||
- Constants third
|
||||
- One blank line before namespace
|
||||
|
||||
**4. Type Hints**
|
||||
- All parameters must have type hints
|
||||
- All return types must be declared
|
||||
- Use nullable types `?Type` when appropriate
|
||||
- Use union types `Type1|Type2` for PHP 8+
|
||||
|
||||
**5. Property Types**
|
||||
```php
|
||||
private ResourceFactory $resourceFactory; // Required type declaration
|
||||
private readonly ResourceFactory $factory; // Readonly for immutability
|
||||
```
|
||||
|
||||
**6. Alignment**
|
||||
```php
|
||||
$config = [
|
||||
'short' => 'value', // Align on =>
|
||||
'longer' => 'another',
|
||||
];
|
||||
```
|
||||
|
||||
## 🔒 Security
|
||||
|
||||
### FAL (File Abstraction Layer)
|
||||
- **Always use FAL:** Never direct file system access
|
||||
- **ResourceFactory:** For retrieving files by UID
|
||||
- **File validation:** Check isDeleted(), isMissing()
|
||||
- **ProcessedFile:** Use process() for image manipulation
|
||||
|
||||
```php
|
||||
// ✅ Good: FAL usage
|
||||
$file = $this->resourceFactory->getFileObject($id);
|
||||
if ($file->isDeleted() || $file->isMissing()) {
|
||||
throw new \Exception('File not found');
|
||||
}
|
||||
|
||||
// ❌ Bad: Direct file access
|
||||
$file = file_get_contents('/var/www/uploads/' . $filename);
|
||||
```
|
||||
|
||||
### Input Validation
|
||||
- **Type cast superglobals:** `(int)($request->getQueryParams()['id'] ?? 0)`
|
||||
- **Validate before use:** Check ranges, formats, existence
|
||||
- **Exit on error:** Use HTTP status codes with `HttpUtility::HTTP_STATUS_*`
|
||||
|
||||
### XSS Prevention
|
||||
- **Fluid templates:** Auto-escaping enabled by default
|
||||
- **JSON responses:** Use `JsonResponse` class
|
||||
- **Localization:** Via `LocalizationUtility::translate()`
|
||||
|
||||
## ✅ PR/Commit Checklist
|
||||
|
||||
### PHP-Specific Checks
|
||||
1. ✅ **Strict types:** `declare(strict_types=1);` in all files
|
||||
2. ✅ **Type hints:** All parameters and return types declared
|
||||
3. ✅ **PHPStan:** Zero errors (`composer ci:test:php:phpstan`)
|
||||
4. ✅ **Code style:** PSR-12/PER-CS2.0 compliant (`make format`)
|
||||
5. ✅ **Rector:** No modernization suggestions (`composer ci:test:php:rector`)
|
||||
6. ✅ **FAL usage:** No direct file system access
|
||||
7. ✅ **DI pattern:** Constructor injection, no `new ClassName()`
|
||||
8. ✅ **PSR-7:** Request/Response for controllers
|
||||
9. ✅ **Documentation:** PHPDoc for public methods
|
||||
|
||||
## 🎓 Good vs Bad Examples
|
||||
|
||||
### ✅ Good: Controller Pattern
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Netresearch\RteCKEditorImage\Controller;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Core\Http\JsonResponse;
|
||||
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||
|
||||
final class SelectImageController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ResourceFactory $resourceFactory
|
||||
) {
|
||||
}
|
||||
|
||||
public function infoAction(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$fileUid = (int)($request->getQueryParams()['fileId'] ?? 0);
|
||||
|
||||
if ($fileUid <= 0) {
|
||||
return new JsonResponse(['error' => 'Invalid file ID'], 400);
|
||||
}
|
||||
|
||||
$file = $this->resourceFactory->getFileObject($fileUid);
|
||||
|
||||
return new JsonResponse([
|
||||
'uid' => $file->getUid(),
|
||||
'width' => $file->getProperty('width'),
|
||||
'height' => $file->getProperty('height'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ❌ Bad: Anti-patterns
|
||||
|
||||
```php
|
||||
<?php
|
||||
// ❌ Missing strict types
|
||||
namespace Netresearch\RteCKEditorImage\Controller;
|
||||
|
||||
// ❌ Missing PSR-7 types
|
||||
class SelectImageController
|
||||
{
|
||||
// ❌ No constructor DI
|
||||
public function infoAction($request)
|
||||
{
|
||||
// ❌ Direct superglobal access
|
||||
$fileUid = $_GET['fileId'];
|
||||
|
||||
// ❌ No DI - manual instantiation
|
||||
$factory = new ResourceFactory();
|
||||
|
||||
// ❌ No type safety, no validation
|
||||
$file = $factory->getFileObject($fileUid);
|
||||
|
||||
// ❌ Manual JSON encoding
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['uid' => $file->getUid()]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ✅ Good: EventListener Pattern
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Netresearch\RteCKEditorImage\EventListener;
|
||||
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\RteCKEditor\Form\Element\Event\AfterPrepareConfigurationForEditorEvent;
|
||||
|
||||
final class RteConfigurationListener
|
||||
{
|
||||
public function __construct(
|
||||
private readonly UriBuilder $uriBuilder
|
||||
) {
|
||||
}
|
||||
|
||||
public function __invoke(AfterPrepareConfigurationForEditorEvent $event): void
|
||||
{
|
||||
$configuration = $event->getConfiguration();
|
||||
$configuration['style']['typo3image'] = [
|
||||
'routeUrl' => (string)$this->uriBuilder->buildUriFromRoute('rteckeditorimage_wizard_select_image'),
|
||||
];
|
||||
$event->setConfiguration($configuration);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ❌ Bad: EventListener Anti-pattern
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Netresearch\RteCKEditorImage\EventListener;
|
||||
|
||||
class RteConfigurationListener
|
||||
{
|
||||
// ❌ Wrong signature - not invokable
|
||||
public function handle($event)
|
||||
{
|
||||
// ❌ Manual instantiation instead of DI
|
||||
$uriBuilder = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(UriBuilder::class);
|
||||
|
||||
// ❌ Array access without type safety
|
||||
$config = $event->getConfiguration();
|
||||
$config['style']['typo3image']['routeUrl'] = $uriBuilder->buildUriFromRoute('rteckeditorimage_wizard_select_image');
|
||||
$event->setConfiguration($config);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ✅ Good: FAL Usage
|
||||
|
||||
```php
|
||||
protected function getImage(int $id): File
|
||||
{
|
||||
try {
|
||||
$file = $this->resourceFactory->getFileObject($id);
|
||||
|
||||
if ($file->isDeleted() || $file->isMissing()) {
|
||||
throw new FileNotFoundException('File not found or deleted', 1234567890);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
throw new FileNotFoundException('Could not load file', 1234567891, $e);
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
```
|
||||
|
||||
### ❌ Bad: Direct File Access
|
||||
|
||||
```php
|
||||
// ❌ Multiple issues
|
||||
protected function getImage($id) // Missing return type, no type hint
|
||||
{
|
||||
// ❌ Direct file system access, bypassing FAL
|
||||
$path = '/var/www/html/fileadmin/' . $id;
|
||||
|
||||
// ❌ No validation, no error handling
|
||||
if (file_exists($path)) {
|
||||
return file_get_contents($path);
|
||||
}
|
||||
|
||||
return null; // ❌ Should throw exception or return typed null
|
||||
}
|
||||
```
|
||||
|
||||
## 🆘 When Stuck
|
||||
|
||||
### Documentation
|
||||
- **API Reference:** [docs/API/Controllers.md](../docs/API/Controllers.md) - Controller APIs
|
||||
- **Event Listeners:** [docs/API/EventListeners.md](../docs/API/EventListeners.md) - PSR-14 events
|
||||
- **Data Handling:** [docs/API/DataHandling.md](../docs/API/DataHandling.md) - Database hooks
|
||||
- **Architecture:** [docs/Architecture/Overview.md](../docs/Architecture/Overview.md) - System design
|
||||
|
||||
### TYPO3 Resources
|
||||
- **FAL Documentation:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Fal/Index.html
|
||||
- **PSR-14 Events:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Events/Index.html
|
||||
- **Dependency Injection:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/DependencyInjection/Index.html
|
||||
- **Controllers:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Backend/Controllers/Index.html
|
||||
|
||||
### Common Issues
|
||||
- **ResourceFactory errors:** Check file exists, not deleted, proper UID
|
||||
- **DI not working:** Verify `Configuration/Services.yaml` registration
|
||||
- **PHPStan errors:** Update baseline: `composer ci:test:php:phpstan:baseline`
|
||||
- **Type errors:** Enable strict_types, add all type hints
|
||||
|
||||
## 📐 House Rules
|
||||
|
||||
### Controllers
|
||||
- **Extend framework controllers:** ElementBrowserController for browsers
|
||||
- **Final by default:** Use `final class` unless inheritance required
|
||||
- **PSR-7 types:** ServerRequestInterface → ResponseInterface
|
||||
- **JSON responses:** Use `JsonResponse` class
|
||||
- **Validation first:** Validate all input parameters at method start
|
||||
|
||||
### EventListeners
|
||||
- **Invokable:** Use `__invoke()` method signature
|
||||
- **Event type hints:** Type-hint specific event classes
|
||||
- **Immutability aware:** Get, modify, set configuration/state
|
||||
- **Final classes:** Event listeners should be final
|
||||
|
||||
### DataHandling
|
||||
- **Soft references:** Implement soft reference parsing for data integrity
|
||||
- **Database hooks:** Use for maintaining referential integrity
|
||||
- **Transaction safety:** Consider rollback scenarios
|
||||
|
||||
### Dependencies
|
||||
- **Constructor injection:** All dependencies via constructor
|
||||
- **Readonly properties:** Use `readonly` for immutable dependencies
|
||||
- **Interface over implementation:** Depend on interfaces when available
|
||||
- **GeneralUtility::makeInstance:** Only for factories or when DI unavailable
|
||||
|
||||
### Error Handling
|
||||
- **Type-specific exceptions:** Use TYPO3 exception hierarchy
|
||||
- **HTTP status codes:** Via HttpUtility constants
|
||||
- **Meaningful messages:** Include context in exception messages
|
||||
- **Log important errors:** Use TYPO3 logging framework
|
||||
|
||||
### Testing
|
||||
- **Functional tests:** For controllers, database operations
|
||||
- **Unit tests:** For utilities, isolated logic
|
||||
- **Mock FAL:** Use TYPO3 testing framework FAL mocks
|
||||
- **Test location:** `Tests/Functional/` and `Tests/Unit/`
|
||||
|
||||
## 🔗 Related
|
||||
|
||||
- **[Resources/AGENTS.md](../Resources/AGENTS.md)** - JavaScript/CKEditor integration
|
||||
- **[Tests/AGENTS.md](../Tests/AGENTS.md)** - Testing patterns
|
||||
- **[Configuration/Services.yaml](../Configuration/Services.yaml)** - DI container configuration
|
||||
- **[docs/API/](../docs/API/)** - Complete API documentation
|
||||
199
skills/agents/scripts/detect-project.sh
Executable file
199
skills/agents/scripts/detect-project.sh
Executable file
@@ -0,0 +1,199 @@
|
||||
#!/usr/bin/env bash
|
||||
# Detect project type, language, version, and build tools
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_DIR="${1:-.}"
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
# Initialize variables
|
||||
LANGUAGE="unknown"
|
||||
VERSION="unknown"
|
||||
BUILD_TOOL="unknown"
|
||||
FRAMEWORK="none"
|
||||
PROJECT_TYPE="unknown"
|
||||
QUALITY_TOOLS=()
|
||||
TEST_FRAMEWORK="unknown"
|
||||
HAS_DOCKER=false
|
||||
CI="none"
|
||||
|
||||
# Detect language and version
|
||||
detect_language() {
|
||||
if [ -f "go.mod" ]; then
|
||||
LANGUAGE="go"
|
||||
VERSION=$(grep '^go ' go.mod | awk '{print $2}' || echo "unknown")
|
||||
BUILD_TOOL="go"
|
||||
TEST_FRAMEWORK="testing"
|
||||
|
||||
# Detect Go project type
|
||||
if [ -d "cmd" ]; then
|
||||
PROJECT_TYPE="go-cli"
|
||||
elif grep -q "github.com/gofiber/fiber" go.mod 2>/dev/null; then
|
||||
PROJECT_TYPE="go-web-app"
|
||||
FRAMEWORK="fiber"
|
||||
elif grep -q "github.com/labstack/echo" go.mod 2>/dev/null; then
|
||||
PROJECT_TYPE="go-web-app"
|
||||
FRAMEWORK="echo"
|
||||
elif grep -q "github.com/gin-gonic/gin" go.mod 2>/dev/null; then
|
||||
PROJECT_TYPE="go-web-app"
|
||||
FRAMEWORK="gin"
|
||||
else
|
||||
PROJECT_TYPE="go-library"
|
||||
fi
|
||||
|
||||
# Detect Go quality tools
|
||||
[ -f ".golangci.yml" ] || [ -f ".golangci.yaml" ] && QUALITY_TOOLS+=("golangci-lint")
|
||||
command -v gofmt &>/dev/null && QUALITY_TOOLS+=("gofmt")
|
||||
|
||||
elif [ -f "composer.json" ]; then
|
||||
LANGUAGE="php"
|
||||
VERSION=$(jq -r '.require.php // "unknown"' composer.json 2>/dev/null || echo "unknown")
|
||||
BUILD_TOOL="composer"
|
||||
|
||||
# Detect PHP framework
|
||||
if jq -e '.require."typo3/cms-core"' composer.json &>/dev/null; then
|
||||
PROJECT_TYPE="php-typo3"
|
||||
FRAMEWORK="typo3"
|
||||
TYPO3_VERSION=$(jq -r '.require."typo3/cms-core"' composer.json 2>/dev/null || echo "unknown")
|
||||
elif jq -e '.require."laravel/framework"' composer.json &>/dev/null; then
|
||||
PROJECT_TYPE="php-laravel"
|
||||
FRAMEWORK="laravel"
|
||||
elif jq -e '.require."symfony/symfony"' composer.json &>/dev/null; then
|
||||
PROJECT_TYPE="php-symfony"
|
||||
FRAMEWORK="symfony"
|
||||
else
|
||||
PROJECT_TYPE="php-library"
|
||||
fi
|
||||
|
||||
# Detect PHP quality tools
|
||||
jq -e '.require."phpstan/phpstan"' composer.json &>/dev/null && QUALITY_TOOLS+=("phpstan")
|
||||
jq -e '.require."friendsofphp/php-cs-fixer"' composer.json &>/dev/null && QUALITY_TOOLS+=("php-cs-fixer")
|
||||
[ -f "phpunit.xml" ] || [ -f "phpunit.xml.dist" ] && TEST_FRAMEWORK="phpunit"
|
||||
|
||||
elif [ -f "package.json" ]; then
|
||||
LANGUAGE="typescript"
|
||||
VERSION=$(jq -r '.engines.node // "unknown"' package.json 2>/dev/null || echo "unknown")
|
||||
|
||||
# Detect JS/TS framework
|
||||
if jq -e '.dependencies."next"' package.json &>/dev/null; then
|
||||
PROJECT_TYPE="typescript-nextjs"
|
||||
FRAMEWORK="next.js"
|
||||
BUILD_TOOL="npm"
|
||||
elif jq -e '.dependencies."react"' package.json &>/dev/null; then
|
||||
PROJECT_TYPE="typescript-react"
|
||||
FRAMEWORK="react"
|
||||
BUILD_TOOL="npm"
|
||||
elif jq -e '.dependencies."vue"' package.json &>/dev/null; then
|
||||
PROJECT_TYPE="typescript-vue"
|
||||
FRAMEWORK="vue"
|
||||
BUILD_TOOL="npm"
|
||||
elif jq -e '.dependencies."express"' package.json &>/dev/null; then
|
||||
PROJECT_TYPE="typescript-node"
|
||||
FRAMEWORK="express"
|
||||
BUILD_TOOL="npm"
|
||||
else
|
||||
PROJECT_TYPE="typescript-library"
|
||||
BUILD_TOOL="npm"
|
||||
fi
|
||||
|
||||
# Check for yarn/pnpm
|
||||
[ -f "yarn.lock" ] && BUILD_TOOL="yarn"
|
||||
[ -f "pnpm-lock.yaml" ] && BUILD_TOOL="pnpm"
|
||||
|
||||
# Detect quality tools
|
||||
jq -e '.devDependencies."eslint"' package.json &>/dev/null && QUALITY_TOOLS+=("eslint")
|
||||
jq -e '.devDependencies."prettier"' package.json &>/dev/null && QUALITY_TOOLS+=("prettier")
|
||||
jq -e '.devDependencies."typescript"' package.json &>/dev/null && QUALITY_TOOLS+=("tsc")
|
||||
|
||||
# Detect test framework
|
||||
if jq -e '.devDependencies."jest"' package.json &>/dev/null; then
|
||||
TEST_FRAMEWORK="jest"
|
||||
elif jq -e '.devDependencies."vitest"' package.json &>/dev/null; then
|
||||
TEST_FRAMEWORK="vitest"
|
||||
fi
|
||||
|
||||
elif [ -f "pyproject.toml" ]; then
|
||||
LANGUAGE="python"
|
||||
VERSION=$(grep 'requires-python' pyproject.toml | cut -d'"' -f2 2>/dev/null || echo "unknown")
|
||||
|
||||
# Detect Python build tool
|
||||
if grep -q '\[tool.poetry\]' pyproject.toml 2>/dev/null; then
|
||||
BUILD_TOOL="poetry"
|
||||
elif grep -q '\[tool.hatch\]' pyproject.toml 2>/dev/null; then
|
||||
BUILD_TOOL="hatch"
|
||||
else
|
||||
BUILD_TOOL="pip"
|
||||
fi
|
||||
|
||||
# Detect framework
|
||||
if grep -q 'django' pyproject.toml 2>/dev/null; then
|
||||
PROJECT_TYPE="python-django"
|
||||
FRAMEWORK="django"
|
||||
elif grep -q 'flask' pyproject.toml 2>/dev/null; then
|
||||
PROJECT_TYPE="python-flask"
|
||||
FRAMEWORK="flask"
|
||||
elif grep -q 'fastapi' pyproject.toml 2>/dev/null; then
|
||||
PROJECT_TYPE="python-fastapi"
|
||||
FRAMEWORK="fastapi"
|
||||
elif [ -d "scripts" ] && [ "$(find scripts -name '*.py' | wc -l)" -gt 3 ]; then
|
||||
PROJECT_TYPE="python-cli"
|
||||
else
|
||||
PROJECT_TYPE="python-library"
|
||||
fi
|
||||
|
||||
# Detect quality tools
|
||||
grep -q 'ruff' pyproject.toml 2>/dev/null && QUALITY_TOOLS+=("ruff")
|
||||
grep -q 'black' pyproject.toml 2>/dev/null && QUALITY_TOOLS+=("black")
|
||||
grep -q 'mypy' pyproject.toml 2>/dev/null && QUALITY_TOOLS+=("mypy")
|
||||
grep -q 'pytest' pyproject.toml 2>/dev/null && TEST_FRAMEWORK="pytest"
|
||||
fi
|
||||
}
|
||||
|
||||
# Detect if Makefile exists
|
||||
if [ -f "Makefile" ]; then
|
||||
BUILD_TOOL="make"
|
||||
fi
|
||||
|
||||
# Detect Docker
|
||||
[ -f "Dockerfile" ] || [ -f "docker-compose.yml" ] && HAS_DOCKER=true
|
||||
|
||||
# Detect CI
|
||||
if [ -d ".github/workflows" ]; then
|
||||
CI="github-actions"
|
||||
elif [ -f ".gitlab-ci.yml" ]; then
|
||||
CI="gitlab-ci"
|
||||
elif [ -f ".circleci/config.yml" ]; then
|
||||
CI="circleci"
|
||||
fi
|
||||
|
||||
# Run detection
|
||||
detect_language
|
||||
|
||||
# Output JSON
|
||||
# Handle empty quality_tools array
|
||||
if [ ${#QUALITY_TOOLS[@]} -eq 0 ]; then
|
||||
TOOLS_JSON="[]"
|
||||
else
|
||||
TOOLS_JSON="$(printf '%s\n' "${QUALITY_TOOLS[@]}" | jq -R . | jq -s .)"
|
||||
fi
|
||||
|
||||
jq -n \
|
||||
--arg type "$PROJECT_TYPE" \
|
||||
--arg lang "$LANGUAGE" \
|
||||
--arg ver "$VERSION" \
|
||||
--arg build "$BUILD_TOOL" \
|
||||
--arg framework "$FRAMEWORK" \
|
||||
--argjson docker "$HAS_DOCKER" \
|
||||
--argjson tools "$TOOLS_JSON" \
|
||||
--arg test "$TEST_FRAMEWORK" \
|
||||
--arg ci "$CI" \
|
||||
'{
|
||||
type: $type,
|
||||
language: $lang,
|
||||
version: $ver,
|
||||
build_tool: $build,
|
||||
framework: $framework,
|
||||
has_docker: $docker,
|
||||
quality_tools: $tools,
|
||||
test_framework: $test,
|
||||
ci: $ci
|
||||
}'
|
||||
178
skills/agents/scripts/detect-scopes.sh
Executable file
178
skills/agents/scripts/detect-scopes.sh
Executable file
@@ -0,0 +1,178 @@
|
||||
#!/usr/bin/env bash
|
||||
# Detect directories that should have scoped AGENTS.md files
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_DIR="${1:-.}"
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
MIN_FILES=5 # Minimum files to warrant scoped AGENTS.md
|
||||
|
||||
# Get project info
|
||||
PROJECT_INFO=$(bash "$(dirname "$0")/detect-project.sh" "$PROJECT_DIR")
|
||||
LANGUAGE=$(echo "$PROJECT_INFO" | jq -r '.language')
|
||||
|
||||
scopes=()
|
||||
|
||||
# Function to count source files in a directory
|
||||
count_source_files() {
|
||||
local dir="$1"
|
||||
local pattern="$2"
|
||||
find "$dir" -maxdepth 3 -type f -name "$pattern" 2>/dev/null | wc -l
|
||||
}
|
||||
|
||||
# Function to add scope
|
||||
add_scope() {
|
||||
local path="$1"
|
||||
local type="$2"
|
||||
local count="$3"
|
||||
|
||||
scopes+=("{\"path\": \"$path\", \"type\": \"$type\", \"files\": $count}")
|
||||
}
|
||||
|
||||
# Language-specific scope detection
|
||||
case "$LANGUAGE" in
|
||||
"go")
|
||||
# Check common Go directories
|
||||
[ -d "internal" ] && {
|
||||
count=$(count_source_files "internal" "*.go")
|
||||
[ "$count" -ge "$MIN_FILES" ] && add_scope "internal" "backend-go" "$count"
|
||||
}
|
||||
|
||||
[ -d "pkg" ] && {
|
||||
count=$(count_source_files "pkg" "*.go")
|
||||
[ "$count" -ge "$MIN_FILES" ] && add_scope "pkg" "backend-go" "$count"
|
||||
}
|
||||
|
||||
[ -d "cmd" ] && {
|
||||
count=$(count_source_files "cmd" "*.go")
|
||||
[ "$count" -ge 3 ] && add_scope "cmd" "cli" "$count"
|
||||
}
|
||||
|
||||
[ -d "examples" ] && {
|
||||
count=$(count_source_files "examples" "*.go")
|
||||
[ "$count" -ge 3 ] && add_scope "examples" "examples" "$count"
|
||||
}
|
||||
|
||||
[ -d "testutil" ] && {
|
||||
count=$(count_source_files "testutil" "*.go")
|
||||
[ "$count" -ge 3 ] && add_scope "testutil" "testing" "$count"
|
||||
}
|
||||
|
||||
[ -d "docs" ] && {
|
||||
count=$(find docs -type f \( -name "*.md" -o -name "*.rst" \) | wc -l)
|
||||
[ "$count" -ge 3 ] && add_scope "docs" "documentation" "$count"
|
||||
}
|
||||
;;
|
||||
|
||||
"php")
|
||||
# Check common PHP directories
|
||||
[ -d "Classes" ] && {
|
||||
count=$(count_source_files "Classes" "*.php")
|
||||
[ "$count" -ge "$MIN_FILES" ] && add_scope "Classes" "backend-php" "$count"
|
||||
}
|
||||
|
||||
[ -d "src" ] && {
|
||||
count=$(count_source_files "src" "*.php")
|
||||
[ "$count" -ge "$MIN_FILES" ] && add_scope "src" "backend-php" "$count"
|
||||
}
|
||||
|
||||
[ -d "Tests" ] && {
|
||||
count=$(count_source_files "Tests" "*.php")
|
||||
[ "$count" -ge 3 ] && add_scope "Tests" "testing" "$count"
|
||||
}
|
||||
|
||||
[ -d "tests" ] && {
|
||||
count=$(count_source_files "tests" "*.php")
|
||||
[ "$count" -ge 3 ] && add_scope "tests" "testing" "$count"
|
||||
}
|
||||
|
||||
[ -d "Documentation" ] && {
|
||||
count=$(find Documentation -type f \( -name "*.rst" -o -name "*.md" \) | wc -l)
|
||||
[ "$count" -ge 3 ] && add_scope "Documentation" "documentation" "$count"
|
||||
}
|
||||
|
||||
[ -d "Resources" ] && {
|
||||
count=$(find Resources -type f | wc -l)
|
||||
[ "$count" -ge 5 ] && add_scope "Resources" "resources" "$count"
|
||||
}
|
||||
;;
|
||||
|
||||
"typescript")
|
||||
# Check common TypeScript/JavaScript directories
|
||||
[ -d "src" ] && {
|
||||
count=$(count_source_files "src" "*.ts")
|
||||
ts_count=$count
|
||||
count=$(count_source_files "src" "*.tsx")
|
||||
tsx_count=$count
|
||||
|
||||
if [ "$tsx_count" -ge "$MIN_FILES" ]; then
|
||||
add_scope "src" "frontend-typescript" "$tsx_count"
|
||||
elif [ "$ts_count" -ge "$MIN_FILES" ]; then
|
||||
add_scope "src" "backend-typescript" "$ts_count"
|
||||
fi
|
||||
}
|
||||
|
||||
[ -d "components" ] && {
|
||||
count=$(count_source_files "components" "*.tsx")
|
||||
[ "$count" -ge "$MIN_FILES" ] && add_scope "components" "frontend-typescript" "$count"
|
||||
}
|
||||
|
||||
[ -d "pages" ] && {
|
||||
count=$(count_source_files "pages" "*.tsx")
|
||||
[ "$count" -ge 3 ] && add_scope "pages" "frontend-typescript" "$count"
|
||||
}
|
||||
|
||||
[ -d "app" ] && {
|
||||
count=$(count_source_files "app" "*.tsx")
|
||||
[ "$count" -ge 3 ] && add_scope "app" "frontend-typescript" "$count"
|
||||
}
|
||||
|
||||
[ -d "server" ] || [ -d "backend" ] && {
|
||||
dir=$([ -d "server" ] && echo "server" || echo "backend")
|
||||
count=$(count_source_files "$dir" "*.ts")
|
||||
[ "$count" -ge "$MIN_FILES" ] && add_scope "$dir" "backend-typescript" "$count"
|
||||
}
|
||||
|
||||
[ -d "__tests__" ] || [ -d "tests" ] && {
|
||||
dir=$([ -d "__tests__" ] && echo "__tests__" || echo "tests")
|
||||
count=$(count_source_files "$dir" "*.test.ts")
|
||||
[ "$count" -ge 3 ] && add_scope "$dir" "testing" "$count"
|
||||
}
|
||||
;;
|
||||
|
||||
"python")
|
||||
# Check common Python directories
|
||||
[ -d "src" ] && {
|
||||
count=$(count_source_files "src" "*.py")
|
||||
[ "$count" -ge "$MIN_FILES" ] && add_scope "src" "backend-python" "$count"
|
||||
}
|
||||
|
||||
[ -d "tests" ] && {
|
||||
count=$(count_source_files "tests" "*.py")
|
||||
[ "$count" -ge 3 ] && add_scope "tests" "testing" "$count"
|
||||
}
|
||||
|
||||
[ -d "scripts" ] && {
|
||||
count=$(count_source_files "scripts" "*.py")
|
||||
[ "$count" -ge 3 ] && add_scope "scripts" "cli" "$count"
|
||||
}
|
||||
|
||||
[ -d "docs" ] && {
|
||||
count=$(find docs -type f \( -name "*.md" -o -name "*.rst" \) | wc -l)
|
||||
[ "$count" -ge 3 ] && add_scope "docs" "documentation" "$count"
|
||||
}
|
||||
;;
|
||||
esac
|
||||
|
||||
# Check for web subdirectories (cross-language)
|
||||
if [ -d "internal/web" ]; then
|
||||
count=$(find internal/web -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" \) | wc -l)
|
||||
[ "$count" -ge "$MIN_FILES" ] && add_scope "internal/web" "frontend-typescript" "$count"
|
||||
fi
|
||||
|
||||
# Output JSON
|
||||
if [ ${#scopes[@]} -eq 0 ]; then
|
||||
echo '{"scopes": []}'
|
||||
else
|
||||
echo "{\"scopes\": [$(IFS=,; echo "${scopes[*]}")]}"
|
||||
fi
|
||||
183
skills/agents/scripts/extract-commands.sh
Executable file
183
skills/agents/scripts/extract-commands.sh
Executable file
@@ -0,0 +1,183 @@
|
||||
#!/usr/bin/env bash
|
||||
# Extract build commands from various build tool files
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_DIR="${1:-.}"
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
# Get project info
|
||||
PROJECT_INFO=$(bash "$(dirname "$0")/detect-project.sh" "$PROJECT_DIR")
|
||||
LANGUAGE=$(echo "$PROJECT_INFO" | jq -r '.language')
|
||||
BUILD_TOOL=$(echo "$PROJECT_INFO" | jq -r '.build_tool')
|
||||
|
||||
# Initialize command variables
|
||||
TYPECHECK_CMD=""
|
||||
LINT_CMD=""
|
||||
FORMAT_CMD=""
|
||||
TEST_CMD=""
|
||||
BUILD_CMD=""
|
||||
DEV_CMD=""
|
||||
|
||||
# Extract from Makefile
|
||||
extract_from_makefile() {
|
||||
[ ! -f "Makefile" ] && return
|
||||
|
||||
# Extract targets with ## comments
|
||||
while IFS= read -r line; do
|
||||
if [[ $line =~ ^([a-zA-Z_-]+):.*\#\#(.*)$ ]]; then
|
||||
target="${BASH_REMATCH[1]}"
|
||||
description="${BASH_REMATCH[2]}"
|
||||
|
||||
case "$target" in
|
||||
lint|check) LINT_CMD="make $target" ;;
|
||||
format|fmt) FORMAT_CMD="make $target" ;;
|
||||
test|tests) TEST_CMD="make $target" ;;
|
||||
build) BUILD_CMD="make $target" ;;
|
||||
typecheck|types) TYPECHECK_CMD="make $target" ;;
|
||||
dev|serve) DEV_CMD="make $target" ;;
|
||||
esac
|
||||
fi
|
||||
done < Makefile
|
||||
}
|
||||
|
||||
# Extract from package.json
|
||||
extract_from_package_json() {
|
||||
[ ! -f "package.json" ] && return
|
||||
|
||||
TYPECHECK_CMD=$(jq -r '.scripts.typecheck // .scripts["type-check"] // empty' package.json 2>/dev/null)
|
||||
[ -n "$TYPECHECK_CMD" ] && TYPECHECK_CMD="npm run typecheck" || TYPECHECK_CMD="npx tsc --noEmit"
|
||||
|
||||
LINT_CMD=$(jq -r '.scripts.lint // empty' package.json 2>/dev/null)
|
||||
[ -n "$LINT_CMD" ] && LINT_CMD="npm run lint" || LINT_CMD="npx eslint ."
|
||||
|
||||
FORMAT_CMD=$(jq -r '.scripts.format // empty' package.json 2>/dev/null)
|
||||
[ -n "$FORMAT_CMD" ] && FORMAT_CMD="npm run format" || FORMAT_CMD="npx prettier --write ."
|
||||
|
||||
TEST_CMD=$(jq -r '.scripts.test // empty' package.json 2>/dev/null)
|
||||
[ -n "$TEST_CMD" ] && TEST_CMD="npm test"
|
||||
|
||||
BUILD_CMD=$(jq -r '.scripts.build // empty' package.json 2>/dev/null)
|
||||
[ -n "$BUILD_CMD" ] && BUILD_CMD="npm run build"
|
||||
|
||||
DEV_CMD=$(jq -r '.scripts.dev // .scripts.start // empty' package.json 2>/dev/null)
|
||||
[ -n "$DEV_CMD" ] && DEV_CMD="npm run dev"
|
||||
}
|
||||
|
||||
# Extract from composer.json
|
||||
extract_from_composer_json() {
|
||||
[ ! -f "composer.json" ] && return
|
||||
|
||||
LINT_CMD=$(jq -r '.scripts.lint // .scripts["cs:check"] // empty' composer.json 2>/dev/null)
|
||||
[ -n "$LINT_CMD" ] && LINT_CMD="composer run lint"
|
||||
|
||||
FORMAT_CMD=$(jq -r '.scripts.format // .scripts["cs:fix"] // empty' composer.json 2>/dev/null)
|
||||
[ -n "$FORMAT_CMD" ] && FORMAT_CMD="composer run format"
|
||||
|
||||
TEST_CMD=$(jq -r '.scripts.test // empty' composer.json 2>/dev/null)
|
||||
[ -n "$TEST_CMD" ] && TEST_CMD="composer run test" || TEST_CMD="vendor/bin/phpunit"
|
||||
|
||||
TYPECHECK_CMD=$(jq -r '.scripts.phpstan // .scripts["stan"] // empty' composer.json 2>/dev/null)
|
||||
[ -n "$TYPECHECK_CMD" ] && TYPECHECK_CMD="composer run phpstan" || {
|
||||
if [ -f "phpstan.neon" ] || [ -f "Build/phpstan.neon" ]; then
|
||||
TYPECHECK_CMD="vendor/bin/phpstan analyze"
|
||||
fi
|
||||
}
|
||||
}
|
||||
|
||||
# Extract from pyproject.toml
|
||||
extract_from_pyproject() {
|
||||
[ ! -f "pyproject.toml" ] && return
|
||||
|
||||
# Check for ruff
|
||||
if grep -q '\[tool.ruff\]' pyproject.toml; then
|
||||
LINT_CMD="ruff check ."
|
||||
FORMAT_CMD="ruff format ."
|
||||
fi
|
||||
|
||||
# Check for black
|
||||
if grep -q 'black' pyproject.toml; then
|
||||
FORMAT_CMD="black ."
|
||||
fi
|
||||
|
||||
# Check for mypy
|
||||
if grep -q 'mypy' pyproject.toml; then
|
||||
TYPECHECK_CMD="mypy ."
|
||||
fi
|
||||
|
||||
# Check for pytest
|
||||
if grep -q 'pytest' pyproject.toml; then
|
||||
TEST_CMD="pytest"
|
||||
fi
|
||||
}
|
||||
|
||||
# Language-specific defaults
|
||||
set_language_defaults() {
|
||||
case "$LANGUAGE" in
|
||||
"go")
|
||||
[ -z "$TYPECHECK_CMD" ] && TYPECHECK_CMD="go build -v ./..."
|
||||
[ -z "$LINT_CMD" ] && {
|
||||
if [ -f ".golangci.yml" ] || [ -f ".golangci.yaml" ]; then
|
||||
LINT_CMD="golangci-lint run ./..."
|
||||
fi
|
||||
}
|
||||
[ -z "$FORMAT_CMD" ] && FORMAT_CMD="gofmt -w ."
|
||||
[ -z "$TEST_CMD" ] && TEST_CMD="go test -v -race -short ./..."
|
||||
[ -z "$BUILD_CMD" ] && BUILD_CMD="go build -v ./..."
|
||||
;;
|
||||
|
||||
"php")
|
||||
[ -z "$TYPECHECK_CMD" ] && {
|
||||
if [ -f "phpstan.neon" ] || [ -f "Build/phpstan.neon" ]; then
|
||||
TYPECHECK_CMD="vendor/bin/phpstan analyze"
|
||||
fi
|
||||
}
|
||||
[ -z "$LINT_CMD" ] && LINT_CMD="vendor/bin/php-cs-fixer fix --dry-run"
|
||||
[ -z "$FORMAT_CMD" ] && FORMAT_CMD="vendor/bin/php-cs-fixer fix"
|
||||
[ -z "$TEST_CMD" ] && TEST_CMD="vendor/bin/phpunit"
|
||||
;;
|
||||
|
||||
"typescript")
|
||||
[ -z "$TYPECHECK_CMD" ] && TYPECHECK_CMD="npx tsc --noEmit"
|
||||
[ -z "$LINT_CMD" ] && LINT_CMD="npx eslint ."
|
||||
[ -z "$FORMAT_CMD" ] && FORMAT_CMD="npx prettier --write ."
|
||||
[ -z "$TEST_CMD" ] && {
|
||||
if [ -f "jest.config.js" ] || [ -f "jest.config.ts" ]; then
|
||||
TEST_CMD="npm test"
|
||||
elif grep -q 'vitest' package.json 2>/dev/null; then
|
||||
TEST_CMD="npx vitest"
|
||||
fi
|
||||
}
|
||||
;;
|
||||
|
||||
"python")
|
||||
[ -z "$LINT_CMD" ] && LINT_CMD="ruff check ."
|
||||
[ -z "$FORMAT_CMD" ] && FORMAT_CMD="ruff format ."
|
||||
[ -z "$TYPECHECK_CMD" ] && TYPECHECK_CMD="mypy ."
|
||||
[ -z "$TEST_CMD" ] && TEST_CMD="pytest"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Run extraction
|
||||
extract_from_makefile
|
||||
extract_from_package_json
|
||||
extract_from_composer_json
|
||||
extract_from_pyproject
|
||||
set_language_defaults
|
||||
|
||||
# Output JSON
|
||||
jq -n \
|
||||
--arg typecheck "$TYPECHECK_CMD" \
|
||||
--arg lint "$LINT_CMD" \
|
||||
--arg format "$FORMAT_CMD" \
|
||||
--arg test "$TEST_CMD" \
|
||||
--arg build "$BUILD_CMD" \
|
||||
--arg dev "$DEV_CMD" \
|
||||
'{
|
||||
typecheck: $typecheck,
|
||||
lint: $lint,
|
||||
format: $format,
|
||||
test: $test,
|
||||
build: $build,
|
||||
dev: $dev
|
||||
}'
|
||||
292
skills/agents/scripts/generate-agents.sh
Executable file
292
skills/agents/scripts/generate-agents.sh
Executable file
@@ -0,0 +1,292 @@
|
||||
#!/usr/bin/env bash
|
||||
# Main AGENTS.md generator script
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SKILL_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
TEMPLATE_DIR="$SKILL_DIR/templates"
|
||||
|
||||
# Source helper library
|
||||
source "$SCRIPT_DIR/lib/template.sh"
|
||||
|
||||
# Default options
|
||||
PROJECT_DIR="${1:-.}"
|
||||
STYLE="${STYLE:-thin}"
|
||||
DRY_RUN=false
|
||||
UPDATE_ONLY=false
|
||||
FORCE=false
|
||||
VERBOSE=false
|
||||
|
||||
# Parse flags
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--style=*)
|
||||
STYLE="${1#*=}"
|
||||
shift
|
||||
;;
|
||||
--dry-run)
|
||||
DRY_RUN=true
|
||||
shift
|
||||
;;
|
||||
--update)
|
||||
UPDATE_ONLY=true
|
||||
shift
|
||||
;;
|
||||
--force)
|
||||
FORCE=true
|
||||
shift
|
||||
;;
|
||||
--verbose|-v)
|
||||
VERBOSE=true
|
||||
shift
|
||||
;;
|
||||
--help|-h)
|
||||
cat <<EOF
|
||||
Usage: generate-agents.sh [PROJECT_DIR] [OPTIONS]
|
||||
|
||||
Generate AGENTS.md files for a project following the public agents.md convention.
|
||||
|
||||
Options:
|
||||
--style=thin|verbose Template style (default: thin)
|
||||
--dry-run Preview what will be created
|
||||
--update Update existing files only
|
||||
--force Force regeneration of existing files
|
||||
--verbose, -v Verbose output
|
||||
--help, -h Show this help message
|
||||
|
||||
Examples:
|
||||
generate-agents.sh . # Generate thin root + scoped files
|
||||
generate-agents.sh . --dry-run # Preview changes
|
||||
generate-agents.sh . --style=verbose # Use verbose root template
|
||||
generate-agents.sh . --update # Update existing files
|
||||
EOF
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
PROJECT_DIR="$1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
log() {
|
||||
[ "$VERBOSE" = true ] && echo "[INFO] $*" >&2
|
||||
}
|
||||
|
||||
error() {
|
||||
echo "[ERROR] $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Detect project
|
||||
log "Detecting project type..."
|
||||
PROJECT_INFO=$("$SCRIPT_DIR/detect-project.sh" "$PROJECT_DIR")
|
||||
[ "$VERBOSE" = true ] && echo "$PROJECT_INFO" | jq . >&2
|
||||
|
||||
LANGUAGE=$(echo "$PROJECT_INFO" | jq -r '.language')
|
||||
VERSION=$(echo "$PROJECT_INFO" | jq -r '.version')
|
||||
PROJECT_TYPE=$(echo "$PROJECT_INFO" | jq -r '.type')
|
||||
|
||||
[ "$LANGUAGE" = "unknown" ] && error "Could not detect project language"
|
||||
|
||||
# Detect scopes
|
||||
log "Detecting scopes..."
|
||||
SCOPES_INFO=$("$SCRIPT_DIR/detect-scopes.sh" "$PROJECT_DIR")
|
||||
[ "$VERBOSE" = true ] && echo "$SCOPES_INFO" | jq . >&2
|
||||
|
||||
# Extract commands
|
||||
log "Extracting build commands..."
|
||||
COMMANDS=$("$SCRIPT_DIR/extract-commands.sh" "$PROJECT_DIR")
|
||||
[ "$VERBOSE" = true ] && echo "$COMMANDS" | jq . >&2
|
||||
|
||||
# Generate root AGENTS.md
|
||||
ROOT_FILE="$PROJECT_DIR/AGENTS.md"
|
||||
|
||||
if [ -f "$ROOT_FILE" ] && [ "$FORCE" = false ] && [ "$UPDATE_ONLY" = false ]; then
|
||||
log "Root AGENTS.md already exists, skipping (use --force to regenerate)"
|
||||
elif [ "$DRY_RUN" = true ]; then
|
||||
echo "[DRY-RUN] Would create/update: $ROOT_FILE"
|
||||
else
|
||||
log "Generating root AGENTS.md..."
|
||||
|
||||
# Select template
|
||||
if [ "$STYLE" = "verbose" ]; then
|
||||
TEMPLATE="$TEMPLATE_DIR/root-verbose.md"
|
||||
else
|
||||
TEMPLATE="$TEMPLATE_DIR/root-thin.md"
|
||||
fi
|
||||
|
||||
# Prepare template variables
|
||||
declare -A vars
|
||||
vars[TIMESTAMP]=$(get_timestamp)
|
||||
vars[LANGUAGE_CONVENTIONS]=$(get_language_conventions "$LANGUAGE" "$VERSION")
|
||||
vars[TYPECHECK_CMD]=$(echo "$COMMANDS" | jq -r '.typecheck')
|
||||
vars[LINT_CMD]=$(echo "$COMMANDS" | jq -r '.lint')
|
||||
vars[FORMAT_CMD]=$(echo "$COMMANDS" | jq -r '.format' | sed 's/^/ (file scope): /')
|
||||
vars[TEST_CMD]=$(echo "$COMMANDS" | jq -r '.test')
|
||||
vars[SCOPE_INDEX]=$(build_scope_index "$SCOPES_INFO")
|
||||
|
||||
# Verbose template additional vars
|
||||
if [ "$STYLE" = "verbose" ]; then
|
||||
vars[PROJECT_DESCRIPTION]="TODO: Add project description"
|
||||
vars[VERSION]="$VERSION"
|
||||
vars[BUILD_TOOL]=$(echo "$PROJECT_INFO" | jq -r '.build_tool')
|
||||
vars[FRAMEWORK]=$(echo "$PROJECT_INFO" | jq -r '.framework')
|
||||
vars[PROJECT_TYPE]="$PROJECT_TYPE"
|
||||
vars[BUILD_CMD]=$(echo "$COMMANDS" | jq -r '.build')
|
||||
vars[QUALITY_STANDARDS]="TODO: Add quality standards"
|
||||
vars[SECURITY_SPECIFIC]="TODO: Add security-specific guidelines"
|
||||
vars[TEST_COVERAGE]="40"
|
||||
vars[TEST_FAST_CMD]=$(echo "$COMMANDS" | jq -r '.test')
|
||||
vars[TEST_FULL_CMD]=$(echo "$COMMANDS" | jq -r '.test')
|
||||
vars[ARCHITECTURE_DOC]="./docs/architecture.md"
|
||||
vars[API_DOC]="./docs/api.md"
|
||||
vars[CONTRIBUTING_DOC]="./CONTRIBUTING.md"
|
||||
fi
|
||||
|
||||
# Language-specific conflict resolution
|
||||
case "$LANGUAGE" in
|
||||
"go")
|
||||
vars[LANGUAGE_SPECIFIC_CONFLICT_RESOLUTION]="- For Go-specific patterns, defer to language idioms and standard library conventions"
|
||||
;;
|
||||
*)
|
||||
vars[LANGUAGE_SPECIFIC_CONFLICT_RESOLUTION]=""
|
||||
;;
|
||||
esac
|
||||
|
||||
# Render template
|
||||
render_template "$TEMPLATE" "$ROOT_FILE" vars
|
||||
|
||||
echo "✅ Created: $ROOT_FILE"
|
||||
fi
|
||||
|
||||
# Generate scoped AGENTS.md files
|
||||
SCOPE_COUNT=$(echo "$SCOPES_INFO" | jq '.scopes | length')
|
||||
|
||||
if [ "$SCOPE_COUNT" -eq 0 ]; then
|
||||
log "No scopes detected (directories with <$MIN_FILES source files)"
|
||||
else
|
||||
log "Generating $SCOPE_COUNT scoped AGENTS.md files..."
|
||||
|
||||
while read -r scope; do
|
||||
SCOPE_PATH=$(echo "$scope" | jq -r '.path')
|
||||
SCOPE_TYPE=$(echo "$scope" | jq -r '.type')
|
||||
SCOPE_FILE="$PROJECT_DIR/$SCOPE_PATH/AGENTS.md"
|
||||
|
||||
if [ -f "$SCOPE_FILE" ] && [ "$FORCE" = false ] && [ "$UPDATE_ONLY" = false ]; then
|
||||
log "Scoped AGENTS.md already exists: $SCOPE_PATH, skipping"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
echo "[DRY-RUN] Would create/update: $SCOPE_FILE"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Select template based on scope type
|
||||
SCOPE_TEMPLATE="$TEMPLATE_DIR/scoped/$SCOPE_TYPE.md"
|
||||
|
||||
if [ ! -f "$SCOPE_TEMPLATE" ]; then
|
||||
log "No template for scope type: $SCOPE_TYPE, skipping $SCOPE_PATH"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Prepare scoped template variables
|
||||
declare -A scope_vars
|
||||
scope_vars[TIMESTAMP]=$(get_timestamp)
|
||||
scope_vars[SCOPE_NAME]=$(basename "$SCOPE_PATH")
|
||||
scope_vars[SCOPE_DESCRIPTION]=$(get_scope_description "$SCOPE_TYPE")
|
||||
scope_vars[FILE_PATH]="<file>"
|
||||
scope_vars[HOUSE_RULES]=""
|
||||
|
||||
# Language-specific variables
|
||||
case "$SCOPE_TYPE" in
|
||||
"backend-go")
|
||||
scope_vars[GO_VERSION]="$VERSION"
|
||||
scope_vars[GO_MINOR_VERSION]=$(echo "$VERSION" | cut -d. -f2)
|
||||
scope_vars[GO_TOOLS]="golangci-lint, gofmt"
|
||||
scope_vars[ENV_VARS]="See .env.example"
|
||||
scope_vars[BUILD_CMD]=$(echo "$COMMANDS" | jq -r '.build')
|
||||
;;
|
||||
|
||||
"backend-php")
|
||||
scope_vars[PHP_VERSION]="$VERSION"
|
||||
FRAMEWORK=$(echo "$PROJECT_INFO" | jq -r '.framework')
|
||||
scope_vars[FRAMEWORK]="$FRAMEWORK"
|
||||
scope_vars[PHP_EXTENSIONS]="json, mbstring, xml"
|
||||
scope_vars[ENV_VARS]="See .env.example"
|
||||
scope_vars[PHPSTAN_LEVEL]="10"
|
||||
scope_vars[BUILD_CMD]=$(echo "$COMMANDS" | jq -r '.build')
|
||||
|
||||
if [ "$FRAMEWORK" = "typo3" ]; then
|
||||
scope_vars[FRAMEWORK_CONVENTIONS]="- TYPO3-specific: Use dependency injection, follow TYPO3 CGL"
|
||||
scope_vars[FRAMEWORK_DOCS]="- TYPO3 documentation: https://docs.typo3.org"
|
||||
else
|
||||
scope_vars[FRAMEWORK_CONVENTIONS]=""
|
||||
scope_vars[FRAMEWORK_DOCS]=""
|
||||
fi
|
||||
;;
|
||||
|
||||
"frontend-typescript")
|
||||
scope_vars[NODE_VERSION]="$VERSION"
|
||||
FRAMEWORK=$(echo "$PROJECT_INFO" | jq -r '.framework')
|
||||
scope_vars[FRAMEWORK]="$FRAMEWORK"
|
||||
scope_vars[PACKAGE_MANAGER]=$(echo "$PROJECT_INFO" | jq -r '.build_tool')
|
||||
scope_vars[ENV_VARS]="See .env.example"
|
||||
scope_vars[BUILD_CMD]=$(echo "$COMMANDS" | jq -r '.build')
|
||||
scope_vars[DEV_CMD]=$(echo "$COMMANDS" | jq -r '.dev')
|
||||
scope_vars[CSS_APPROACH]="CSS Modules"
|
||||
|
||||
case "$FRAMEWORK" in
|
||||
"react")
|
||||
scope_vars[FRAMEWORK_CONVENTIONS]="- Use functional components with hooks\n- Avoid class components"
|
||||
scope_vars[FRAMEWORK_DOCS]="https://react.dev"
|
||||
;;
|
||||
"next.js")
|
||||
scope_vars[FRAMEWORK_CONVENTIONS]="- Use App Router (app/)\n- Server Components by default"
|
||||
scope_vars[FRAMEWORK_DOCS]="https://nextjs.org/docs"
|
||||
;;
|
||||
"vue")
|
||||
scope_vars[FRAMEWORK_CONVENTIONS]="- Use Composition API\n- Avoid Options API for new code"
|
||||
scope_vars[FRAMEWORK_DOCS]="https://vuejs.org/guide"
|
||||
;;
|
||||
*)
|
||||
scope_vars[FRAMEWORK_CONVENTIONS]=""
|
||||
scope_vars[FRAMEWORK_DOCS]=""
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
|
||||
"cli")
|
||||
scope_vars[LANGUAGE]="$LANGUAGE"
|
||||
CLI_FRAMEWORK="standard"
|
||||
[ -f "go.mod" ] && grep -q "github.com/spf13/cobra" go.mod 2>/dev/null && CLI_FRAMEWORK="cobra"
|
||||
[ -f "go.mod" ] && grep -q "github.com/urfave/cli" go.mod 2>/dev/null && CLI_FRAMEWORK="urfave/cli"
|
||||
scope_vars[CLI_FRAMEWORK]="$CLI_FRAMEWORK"
|
||||
scope_vars[BUILD_OUTPUT_PATH]="./bin/"
|
||||
scope_vars[SETUP_INSTRUCTIONS]="- Build: $(echo "$COMMANDS" | jq -r '.build')"
|
||||
scope_vars[BUILD_CMD]=$(echo "$COMMANDS" | jq -r '.build')
|
||||
scope_vars[RUN_CMD]="./bin/$(basename "$PROJECT_DIR")"
|
||||
scope_vars[TEST_CMD]=$(echo "$COMMANDS" | jq -r '.test')
|
||||
scope_vars[LINT_CMD]=$(echo "$COMMANDS" | jq -r '.lint')
|
||||
;;
|
||||
esac
|
||||
|
||||
# Render template
|
||||
render_template "$SCOPE_TEMPLATE" "$SCOPE_FILE" scope_vars
|
||||
|
||||
echo "✅ Created: $SCOPE_FILE"
|
||||
|
||||
done < <(echo "$SCOPES_INFO" | jq -c '.scopes[]')
|
||||
fi
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
echo ""
|
||||
echo "[DRY-RUN] No files were modified. Remove --dry-run to apply changes."
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ AGENTS.md generation complete!"
|
||||
[ "$SCOPE_COUNT" -gt 0 ] && echo " Generated: 1 root + $SCOPE_COUNT scoped files"
|
||||
178
skills/agents/scripts/validate-structure.sh
Executable file
178
skills/agents/scripts/validate-structure.sh
Executable file
@@ -0,0 +1,178 @@
|
||||
#!/usr/bin/env bash
|
||||
# Validate AGENTS.md structure compliance
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_DIR="${1:-.}"
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
ERRORS=0
|
||||
WARNINGS=0
|
||||
|
||||
error() {
|
||||
echo "❌ ERROR: $*"
|
||||
((ERRORS++))
|
||||
}
|
||||
|
||||
warning() {
|
||||
echo "⚠️ WARNING: $*"
|
||||
((WARNINGS++))
|
||||
}
|
||||
|
||||
success() {
|
||||
echo "✅ $*"
|
||||
}
|
||||
|
||||
# Check if file has managed header
|
||||
check_managed_header() {
|
||||
local file="$1"
|
||||
|
||||
if grep -q "^<!-- Managed by agent:" "$file"; then
|
||||
success "Managed header present: $file"
|
||||
return 0
|
||||
else
|
||||
warning "Missing managed header: $file"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if root is thin (≤50 lines or has scope index)
|
||||
check_root_is_thin() {
|
||||
local file="$1"
|
||||
local line_count=$(wc -l < "$file")
|
||||
|
||||
if [ "$line_count" -le 50 ]; then
|
||||
success "Root is thin: $line_count lines"
|
||||
return 0
|
||||
elif grep -q "## Index of scoped AGENTS.md" "$file"; then
|
||||
success "Root has scope index (verbose style acceptable)"
|
||||
return 0
|
||||
else
|
||||
error "Root is bloated: $line_count lines and no scope index"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if root has precedence statement
|
||||
check_precedence_statement() {
|
||||
local file="$1"
|
||||
|
||||
if grep -qi "precedence" "$file" && grep -qi "closest.*AGENTS.md.*wins" "$file"; then
|
||||
success "Precedence statement present"
|
||||
return 0
|
||||
else
|
||||
error "Missing precedence statement in root"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if scoped file has all 9 sections
|
||||
check_scoped_sections() {
|
||||
local file="$1"
|
||||
local required_sections=(
|
||||
"## Overview"
|
||||
"## Setup & environment"
|
||||
"## Build & tests"
|
||||
"## Code style & conventions"
|
||||
"## Security & safety"
|
||||
"## PR/commit checklist"
|
||||
"## Good vs. bad examples"
|
||||
"## When stuck"
|
||||
)
|
||||
|
||||
local missing=()
|
||||
|
||||
for section in "${required_sections[@]}"; do
|
||||
if ! grep -q "^$section" "$file"; then
|
||||
missing+=("$section")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#missing[@]} -eq 0 ]; then
|
||||
success "All required sections present: $file"
|
||||
return 0
|
||||
else
|
||||
error "Missing sections in $file: ${missing[*]}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if scope index links work
|
||||
check_scope_links() {
|
||||
local root_file="$1"
|
||||
|
||||
if ! grep -q "## Index of scoped AGENTS.md" "$root_file"; then
|
||||
return 0 # No index, skip check
|
||||
fi
|
||||
|
||||
# Extract links from scope index
|
||||
local links=$(sed -n '/## Index of scoped AGENTS.md/,/^##/p' "$root_file" | grep -o '\./[^)]*AGENTS.md' || true)
|
||||
|
||||
if [ -z "$links" ]; then
|
||||
warning "Scope index present but no links found"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local broken=()
|
||||
while read -r link; do
|
||||
# Remove leading ./
|
||||
local clean_link="${link#./}"
|
||||
local full_path="$PROJECT_DIR/$clean_link"
|
||||
|
||||
if [ ! -f "$full_path" ]; then
|
||||
broken+=("$link")
|
||||
fi
|
||||
done <<< "$links"
|
||||
|
||||
if [ ${#broken[@]} -eq 0 ]; then
|
||||
success "All scope index links work"
|
||||
return 0
|
||||
else
|
||||
error "Broken scope index links: ${broken[*]}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Main validation
|
||||
echo "Validating AGENTS.md structure in: $PROJECT_DIR"
|
||||
echo ""
|
||||
|
||||
# Check root AGENTS.md
|
||||
ROOT_FILE="$PROJECT_DIR/AGENTS.md"
|
||||
|
||||
if [ ! -f "$ROOT_FILE" ]; then
|
||||
error "Root AGENTS.md not found"
|
||||
else
|
||||
echo "=== Root AGENTS.md ==="
|
||||
check_managed_header "$ROOT_FILE"
|
||||
check_root_is_thin "$ROOT_FILE"
|
||||
check_precedence_statement "$ROOT_FILE"
|
||||
check_scope_links "$ROOT_FILE"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Check scoped AGENTS.md files
|
||||
SCOPED_FILES=$(find "$PROJECT_DIR" -name "AGENTS.md" -not -path "$ROOT_FILE" 2>/dev/null || true)
|
||||
|
||||
if [ -n "$SCOPED_FILES" ]; then
|
||||
echo "=== Scoped AGENTS.md Files ==="
|
||||
while read -r file; do
|
||||
rel_path="${file#$PROJECT_DIR/}"
|
||||
echo "Checking: $rel_path"
|
||||
check_managed_header "$file"
|
||||
check_scoped_sections "$file"
|
||||
echo ""
|
||||
done <<< "$SCOPED_FILES"
|
||||
fi
|
||||
|
||||
# Summary
|
||||
echo "=== Validation Summary ==="
|
||||
if [ $ERRORS -eq 0 ] && [ $WARNINGS -eq 0 ]; then
|
||||
echo "✅ All checks passed!"
|
||||
exit 0
|
||||
elif [ $ERRORS -eq 0 ]; then
|
||||
echo "⚠️ Validation passed with $WARNINGS warning(s)"
|
||||
exit 0
|
||||
else
|
||||
echo "❌ Validation failed with $ERRORS error(s) and $WARNINGS warning(s)"
|
||||
exit 1
|
||||
fi
|
||||
24
skills/agents/templates/root-thin.md
Normal file
24
skills/agents/templates/root-thin.md
Normal file
@@ -0,0 +1,24 @@
|
||||
<!-- Managed by agent: keep sections and order; edit content, not structure. Last updated: {{TIMESTAMP}} -->
|
||||
|
||||
# AGENTS.md (root)
|
||||
|
||||
This file explains repo-wide conventions and where to find scoped rules.
|
||||
**Precedence:** the **closest `AGENTS.md`** to the files you're changing wins. Root holds global defaults only.
|
||||
|
||||
## Global rules
|
||||
- Keep diffs small; add tests for new code paths
|
||||
- Ask first before: adding heavy deps, running full e2e suites, or repo-wide rewrites
|
||||
- Never commit secrets or sensitive data to the repository
|
||||
{{LANGUAGE_CONVENTIONS}}
|
||||
|
||||
## Minimal pre-commit checks
|
||||
- Typecheck: {{TYPECHECK_CMD}}
|
||||
- Lint/format: {{LINT_CMD}}{{FORMAT_CMD}}
|
||||
- Tests: {{TEST_CMD}}
|
||||
|
||||
## Index of scoped AGENTS.md
|
||||
{{SCOPE_INDEX}}
|
||||
|
||||
## When instructions conflict
|
||||
- The nearest `AGENTS.md` wins. Explicit user prompts override files.
|
||||
{{LANGUAGE_SPECIFIC_CONFLICT_RESOLUTION}}
|
||||
63
skills/agents/templates/root-verbose.md
Normal file
63
skills/agents/templates/root-verbose.md
Normal file
@@ -0,0 +1,63 @@
|
||||
<!-- Managed by agent: keep sections and order; edit content, not structure. Last updated: {{TIMESTAMP}} -->
|
||||
|
||||
# AGENTS.md (root)
|
||||
|
||||
**Precedence:** The **closest AGENTS.md** to changed files wins. Root holds global defaults only.
|
||||
|
||||
## Project Overview
|
||||
{{PROJECT_DESCRIPTION}}
|
||||
|
||||
**Tech Stack**: {{LANGUAGE}} {{VERSION}}, {{BUILD_TOOL}}, {{FRAMEWORK}}
|
||||
**Type**: {{PROJECT_TYPE}}
|
||||
|
||||
## Global Rules
|
||||
- Keep PRs small (~≤300 net LOC)
|
||||
- Conventional Commits: `type(scope): subject`
|
||||
- Ask before: heavy deps, full e2e, repo rewrites
|
||||
- Never commit secrets or PII
|
||||
{{LANGUAGE_CONVENTIONS}}
|
||||
|
||||
## Development Workflow
|
||||
1. Create feature branch: `git checkout -b feature/description`
|
||||
2. Make changes with tests
|
||||
3. Run pre-commit checks (see below)
|
||||
4. Commit with conventional format
|
||||
5. Push and create PR
|
||||
6. Address review feedback
|
||||
7. Merge when approved
|
||||
|
||||
## Pre-commit Checks
|
||||
**Always run before committing:**
|
||||
- Typecheck: {{TYPECHECK_CMD}}
|
||||
- Lint: {{LINT_CMD}}
|
||||
- Format: {{FORMAT_CMD}}
|
||||
- Tests: {{TEST_CMD}}
|
||||
- Build: {{BUILD_CMD}}
|
||||
|
||||
## Code Quality Standards
|
||||
{{QUALITY_STANDARDS}}
|
||||
|
||||
## Security & Safety
|
||||
- Never commit secrets, credentials, or PII
|
||||
- Validate all user inputs
|
||||
- Use parameterized queries for database access
|
||||
- Keep dependencies updated
|
||||
{{SECURITY_SPECIFIC}}
|
||||
|
||||
## Testing Requirements
|
||||
- Write tests for new features
|
||||
- Maintain {{TEST_COVERAGE}}% minimum coverage
|
||||
- Run fast tests locally: {{TEST_FAST_CMD}}
|
||||
- Run full suite in CI: {{TEST_FULL_CMD}}
|
||||
|
||||
## Index of Scoped AGENTS.md
|
||||
{{SCOPE_INDEX}}
|
||||
|
||||
## When Instructions Conflict
|
||||
Nearest AGENTS.md wins. User prompts override files.
|
||||
{{LANGUAGE_SPECIFIC_CONFLICT_RESOLUTION}}
|
||||
|
||||
## Documentation
|
||||
- Architecture: {{ARCHITECTURE_DOC}}
|
||||
- API docs: {{API_DOC}}
|
||||
- Contributing: {{CONTRIBUTING_DOC}}
|
||||
77
skills/agents/templates/scoped/backend-go.md
Normal file
77
skills/agents/templates/scoped/backend-go.md
Normal file
@@ -0,0 +1,77 @@
|
||||
<!-- Managed by agent: keep sections and order; edit content, not structure. Last updated: {{TIMESTAMP}} -->
|
||||
|
||||
# AGENTS.md — {{SCOPE_NAME}}
|
||||
|
||||
## Overview
|
||||
{{SCOPE_DESCRIPTION}}
|
||||
|
||||
## Setup & environment
|
||||
- Install: `go mod download`
|
||||
- Go version: {{GO_VERSION}}
|
||||
- Required tools: {{GO_TOOLS}}
|
||||
- Environment variables: {{ENV_VARS}}
|
||||
|
||||
## Build & tests (prefer file-scoped)
|
||||
- Typecheck a file: `go build -v {{FILE_PATH}}`
|
||||
- Format a file: `gofmt -w {{FILE_PATH}}`
|
||||
- Lint a file: `golangci-lint run {{FILE_PATH}}`
|
||||
- Test a file: `go test -v -race -short {{FILE_PATH}}`
|
||||
- Build: {{BUILD_CMD}}
|
||||
|
||||
## Code style & conventions
|
||||
- Follow Go 1.{{GO_MINOR_VERSION}} idioms
|
||||
- Use standard library over external deps when possible
|
||||
- Errors: wrap with `fmt.Errorf("context: %w", err)`
|
||||
- Naming: `camelCase` for private, `PascalCase` for exported
|
||||
- Struct tags: use canonical form (json, yaml, etc.)
|
||||
- Comments: complete sentences ending with period
|
||||
- Package docs: first sentence summarizes purpose
|
||||
|
||||
## Security & safety
|
||||
- Validate all inputs from external sources
|
||||
- Use `context.Context` for cancellation and timeouts
|
||||
- Avoid goroutine leaks: always ensure termination paths
|
||||
- Sensitive data: never log or include in errors
|
||||
- SQL: use parameterized queries only
|
||||
- File paths: validate and sanitize user-provided paths
|
||||
|
||||
## PR/commit checklist
|
||||
- [ ] Tests pass: `go test -v -race ./...`
|
||||
- [ ] Lint clean: `golangci-lint run ./...`
|
||||
- [ ] Formatted: `gofmt -w .`
|
||||
- [ ] No goroutine leaks
|
||||
- [ ] Error messages are descriptive
|
||||
- [ ] Public APIs have godoc comments
|
||||
|
||||
## Good vs. bad examples
|
||||
**Good**: Descriptive error wrapping
|
||||
```go
|
||||
if err := db.Query(); err != nil {
|
||||
return fmt.Errorf("failed to query users table: %w", err)
|
||||
}
|
||||
```
|
||||
|
||||
**Bad**: Generic error messages
|
||||
```go
|
||||
if err := db.Query(); err != nil {
|
||||
return fmt.Errorf("error: %w", err)
|
||||
}
|
||||
```
|
||||
|
||||
**Good**: Proper context usage
|
||||
```go
|
||||
func (s *Service) FetchData(ctx context.Context, id string) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
return s.client.Get(ctx, id)
|
||||
}
|
||||
```
|
||||
|
||||
## When stuck
|
||||
- Check Go documentation: https://pkg.go.dev
|
||||
- Review existing patterns in this codebase
|
||||
- Check root AGENTS.md for project-wide conventions
|
||||
- Run `go doc <package>` for standard library help
|
||||
|
||||
## House Rules (optional)
|
||||
{{HOUSE_RULES}}
|
||||
85
skills/agents/templates/scoped/backend-php.md
Normal file
85
skills/agents/templates/scoped/backend-php.md
Normal file
@@ -0,0 +1,85 @@
|
||||
<!-- Managed by agent: keep sections and order; edit content, not structure. Last updated: {{TIMESTAMP}} -->
|
||||
|
||||
# AGENTS.md — {{SCOPE_NAME}}
|
||||
|
||||
## Overview
|
||||
{{SCOPE_DESCRIPTION}}
|
||||
|
||||
## Setup & environment
|
||||
- Install: `composer install`
|
||||
- PHP version: {{PHP_VERSION}}
|
||||
- Framework: {{FRAMEWORK}}
|
||||
- Required extensions: {{PHP_EXTENSIONS}}
|
||||
- Environment variables: {{ENV_VARS}}
|
||||
|
||||
## Build & tests (prefer file-scoped)
|
||||
- Typecheck a file: `vendor/bin/phpstan analyze {{FILE_PATH}} --level={{PHPSTAN_LEVEL}}`
|
||||
- Format a file: `vendor/bin/php-cs-fixer fix {{FILE_PATH}}`
|
||||
- Lint a file: `php -l {{FILE_PATH}}`
|
||||
- Test a file: `vendor/bin/phpunit {{FILE_PATH}}`
|
||||
- Build: {{BUILD_CMD}}
|
||||
|
||||
## Code style & conventions
|
||||
- Follow PSR-12 coding standard
|
||||
- Use strict types: `declare(strict_types=1);`
|
||||
- Type hints: always use for parameters and return types
|
||||
- Naming: `camelCase` for methods, `PascalCase` for classes
|
||||
- Visibility: always declare (public, protected, private)
|
||||
- PHPDoc: required for public APIs, include `@param` and `@return`
|
||||
{{FRAMEWORK_CONVENTIONS}}
|
||||
|
||||
## Security & safety
|
||||
- Validate and sanitize all user inputs
|
||||
- Use prepared statements for database queries
|
||||
- Escape output in templates
|
||||
- Never use `eval()` or dynamic code execution
|
||||
- Sensitive data: never log or expose in errors
|
||||
- CSRF protection: enable for all forms
|
||||
- XSS protection: escape all user-generated content
|
||||
|
||||
## PR/commit checklist
|
||||
- [ ] Tests pass: `vendor/bin/phpunit`
|
||||
- [ ] PHPStan Level {{PHPSTAN_LEVEL}} clean: `vendor/bin/phpstan analyze`
|
||||
- [ ] PSR-12 compliant: `vendor/bin/php-cs-fixer fix --dry-run`
|
||||
- [ ] No deprecated functions used
|
||||
- [ ] Public methods have PHPDoc
|
||||
- [ ] Security: inputs validated, outputs escaped
|
||||
|
||||
## Good vs. bad examples
|
||||
**Good**: Proper type hints and strict types
|
||||
```php
|
||||
declare(strict_types=1);
|
||||
|
||||
public function calculateTotal(int $quantity, float $price): float
|
||||
{
|
||||
return $quantity * $price;
|
||||
}
|
||||
```
|
||||
|
||||
**Bad**: Missing type hints
|
||||
```php
|
||||
public function calculateTotal($quantity, $price)
|
||||
{
|
||||
return $quantity * $price;
|
||||
}
|
||||
```
|
||||
|
||||
**Good**: Prepared statements
|
||||
```php
|
||||
$stmt = $db->prepare('SELECT * FROM users WHERE id = :id');
|
||||
$stmt->execute(['id' => $userId]);
|
||||
```
|
||||
|
||||
**Bad**: String concatenation
|
||||
```php
|
||||
$result = $db->query("SELECT * FROM users WHERE id = " . $userId);
|
||||
```
|
||||
|
||||
## When stuck
|
||||
- Check PHP documentation: https://www.php.net
|
||||
- {{FRAMEWORK_DOCS}}
|
||||
- Review existing patterns in this codebase
|
||||
- Check root AGENTS.md for project-wide conventions
|
||||
|
||||
## House Rules (optional)
|
||||
{{HOUSE_RULES}}
|
||||
84
skills/agents/templates/scoped/cli.md
Normal file
84
skills/agents/templates/scoped/cli.md
Normal file
@@ -0,0 +1,84 @@
|
||||
<!-- Managed by agent: keep sections and order; edit content, not structure. Last updated: {{TIMESTAMP}} -->
|
||||
|
||||
# AGENTS.md — {{SCOPE_NAME}}
|
||||
|
||||
## Overview
|
||||
{{SCOPE_DESCRIPTION}}
|
||||
|
||||
Command-line interface tools and entry points.
|
||||
|
||||
## Setup & environment
|
||||
{{SETUP_INSTRUCTIONS}}
|
||||
- CLI framework: {{CLI_FRAMEWORK}}
|
||||
- Build output: {{BUILD_OUTPUT_PATH}}
|
||||
|
||||
## Build & tests (prefer file-scoped)
|
||||
- Build CLI: {{BUILD_CMD}}
|
||||
- Run CLI: {{RUN_CMD}}
|
||||
- Test: {{TEST_CMD}}
|
||||
- Lint: {{LINT_CMD}}
|
||||
|
||||
## Code style & conventions
|
||||
- Use flag parsing library consistently ({{CLI_FRAMEWORK}})
|
||||
- Provide `--help` for all commands and subcommands
|
||||
- Use `--version` to display version information
|
||||
- Exit codes: 0 = success, 1 = general error, 2 = usage error
|
||||
- Output: structured (JSON) for scripts, human-readable for interactive
|
||||
- Errors: write to stderr, not stdout
|
||||
- Progress: show for long-running operations
|
||||
- Interactive prompts: support non-interactive mode with flags
|
||||
|
||||
## Security & safety
|
||||
- Validate all file paths and prevent directory traversal
|
||||
- Never execute user-provided code without explicit confirmation
|
||||
- Sensitive data: never log or display in plain text
|
||||
- Config files: validate schema and permissions
|
||||
- Network operations: timeout and retry with backoff
|
||||
|
||||
## PR/commit checklist
|
||||
- [ ] `--help` text is clear and accurate
|
||||
- [ ] `--version` displays correct version
|
||||
- [ ] Exit codes are correct
|
||||
- [ ] Errors go to stderr
|
||||
- [ ] Long operations show progress
|
||||
- [ ] Works in non-interactive mode
|
||||
- [ ] Tests cover main workflows
|
||||
|
||||
## Good vs. bad examples
|
||||
**Good**: Proper error handling
|
||||
```{{LANGUAGE}}
|
||||
if err := runCommand(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
```
|
||||
|
||||
**Bad**: Errors to stdout
|
||||
```{{LANGUAGE}}
|
||||
if err := runCommand(); err != nil {
|
||||
fmt.Println("Error:", err)
|
||||
}
|
||||
```
|
||||
|
||||
**Good**: Clear help text
|
||||
```
|
||||
Usage: myapp <command> [options]
|
||||
|
||||
Commands:
|
||||
init Initialize a new project
|
||||
build Build the project
|
||||
deploy Deploy to production
|
||||
|
||||
Options:
|
||||
--config string Config file path (default: config.yaml)
|
||||
--verbose Enable verbose output
|
||||
```
|
||||
|
||||
## When stuck
|
||||
- Review {{CLI_FRAMEWORK}} documentation
|
||||
- Check existing commands for patterns
|
||||
- Test with `--help` to ensure clarity
|
||||
- Check root AGENTS.md for project conventions
|
||||
|
||||
## House Rules (optional)
|
||||
{{HOUSE_RULES}}
|
||||
96
skills/agents/templates/scoped/frontend-typescript.md
Normal file
96
skills/agents/templates/scoped/frontend-typescript.md
Normal file
@@ -0,0 +1,96 @@
|
||||
<!-- Managed by agent: keep sections and order; edit content, not structure. Last updated: {{TIMESTAMP}} -->
|
||||
|
||||
# AGENTS.md — {{SCOPE_NAME}}
|
||||
|
||||
## Overview
|
||||
{{SCOPE_DESCRIPTION}}
|
||||
|
||||
## Setup & environment
|
||||
- Install: `npm install` or `yarn install`
|
||||
- Node version: {{NODE_VERSION}}
|
||||
- Framework: {{FRAMEWORK}}
|
||||
- Package manager: {{PACKAGE_MANAGER}}
|
||||
- Environment variables: {{ENV_VARS}}
|
||||
|
||||
## Build & tests (prefer file-scoped)
|
||||
- Typecheck a file: `npx tsc --noEmit {{FILE_PATH}}`
|
||||
- Lint a file: `npx eslint {{FILE_PATH}}`
|
||||
- Format a file: `npx prettier --write {{FILE_PATH}}`
|
||||
- Test a file: `npm test {{FILE_PATH}}`
|
||||
- Build: {{BUILD_CMD}}
|
||||
- Dev server: {{DEV_CMD}}
|
||||
|
||||
## Code style & conventions
|
||||
- TypeScript strict mode enabled
|
||||
- Use functional components with hooks (React)
|
||||
- Naming: `camelCase` for variables/functions, `PascalCase` for components
|
||||
- File naming: `ComponentName.tsx`, `utilityName.ts`
|
||||
- Imports: group and sort (external, internal, types)
|
||||
- CSS: {{CSS_APPROACH}} (CSS Modules, Tailwind, styled-components, etc.)
|
||||
{{FRAMEWORK_CONVENTIONS}}
|
||||
|
||||
## Security & safety
|
||||
- Sanitize user inputs before rendering
|
||||
- Use `dangerouslySetInnerHTML` only with sanitized content
|
||||
- Validate environment variables at build time
|
||||
- Never expose secrets in client-side code
|
||||
- Use HTTPS for all API calls
|
||||
- Implement CSP headers
|
||||
- WCAG 2.2 AA accessibility compliance
|
||||
|
||||
## PR/commit checklist
|
||||
- [ ] Tests pass: `npm test`
|
||||
- [ ] TypeScript compiles: `npx tsc --noEmit`
|
||||
- [ ] Lint clean: `npm run lint`
|
||||
- [ ] Formatted: `npm run format`
|
||||
- [ ] Accessibility: keyboard navigation works, ARIA labels present
|
||||
- [ ] Responsive: tested on mobile, tablet, desktop
|
||||
- [ ] Performance: no unnecessary re-renders
|
||||
|
||||
## Good vs. bad examples
|
||||
**Good**: Proper TypeScript typing
|
||||
```typescript
|
||||
interface User {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
function UserCard({ user }: { user: User }): JSX.Element {
|
||||
return <div>{user.name}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
**Bad**: Using `any`
|
||||
```typescript
|
||||
function UserCard({ user }: { user: any }) {
|
||||
return <div>{user.name}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
**Good**: Accessible button
|
||||
```tsx
|
||||
<button
|
||||
onClick={handleClick}
|
||||
aria-label="Close dialog"
|
||||
type="button"
|
||||
>
|
||||
<CloseIcon />
|
||||
</button>
|
||||
```
|
||||
|
||||
**Bad**: Non-semantic click handler
|
||||
```tsx
|
||||
<div onClick={handleClick}>
|
||||
<CloseIcon />
|
||||
</div>
|
||||
```
|
||||
|
||||
## When stuck
|
||||
- Check {{FRAMEWORK}} documentation: {{FRAMEWORK_DOCS}}
|
||||
- Review TypeScript handbook: https://www.typescriptlang.org/docs/
|
||||
- Check root AGENTS.md for project-wide conventions
|
||||
- Review existing components for patterns
|
||||
|
||||
## House Rules (optional)
|
||||
{{HOUSE_RULES}}
|
||||
345
skills/netresearch-branding/README.md
Normal file
345
skills/netresearch-branding/README.md
Normal file
@@ -0,0 +1,345 @@
|
||||
# Netresearch Brand Guidelines Skill
|
||||
|
||||
A comprehensive Claude Code skill for implementing Netresearch brand guidelines in web projects. Ensures consistent brand application across all digital touchpoints.
|
||||
|
||||
## Overview
|
||||
|
||||
This skill provides complete brand guidelines, reference documentation, and ready-to-use templates for implementing Netresearch's visual identity on web projects. It enables Claude Code to help developers and designers maintain brand consistency across websites, applications, and digital materials.
|
||||
|
||||
## Features
|
||||
|
||||
### 📋 Comprehensive Brand Guidelines
|
||||
- **SKILL.md** - Complete brand implementation guide covering:
|
||||
- Brand identity and core values
|
||||
- Color system with accessibility compliance
|
||||
- Typography standards (Raleway, Open Sans, Calibri)
|
||||
- Logo usage guidelines and restrictions
|
||||
- Web design principles and best practices
|
||||
- Component styles and patterns
|
||||
- Social media specifications
|
||||
- Image guidelines
|
||||
- Accessibility requirements (WCAG AA)
|
||||
- Implementation checklists
|
||||
|
||||
### 🎨 Reference Documentation
|
||||
- **colors.md** - Detailed color palette with:
|
||||
- Technical specifications (Hex, RGB, CMYK, Pantone, NCS)
|
||||
- Usage guidelines and approved combinations
|
||||
- WCAG AA contrast ratios
|
||||
- CSS custom properties
|
||||
- Accessibility compliance details
|
||||
|
||||
- **typography.md** - Complete typography system with:
|
||||
- Font specifications and licensing
|
||||
- Web font loading strategies
|
||||
- Typography scale and hierarchy
|
||||
- Responsive typography patterns
|
||||
- Line height and spacing guidelines
|
||||
- Performance optimization
|
||||
|
||||
- **web-design.md** - Web implementation patterns:
|
||||
- Responsive grid system
|
||||
- Component library (buttons, cards, forms, navigation)
|
||||
- Layout patterns
|
||||
- Animations and transitions
|
||||
- Accessibility considerations
|
||||
|
||||
### 🎯 Templates & Examples
|
||||
- **landing-page.html** - Complete landing page template
|
||||
- **styles.css** - Production-ready brand CSS with all components
|
||||
- **components.html** - Interactive component showcase
|
||||
|
||||
## Installation
|
||||
|
||||
### Via Claude Code Marketplace
|
||||
|
||||
```bash
|
||||
/plugin marketplace add netresearch/claude-code-marketplace
|
||||
/plugin install netresearch-branding
|
||||
```
|
||||
|
||||
### Manual Installation
|
||||
|
||||
```bash
|
||||
# Using curl
|
||||
curl -L https://github.com/netresearch/netresearch-branding-skill/archive/refs/heads/main.zip -o netresearch-branding.zip
|
||||
unzip netresearch-branding.zip -d ~/.claude/skills/
|
||||
mv ~/.claude/skills/netresearch-branding-skill-main ~/.claude/skills/netresearch-branding
|
||||
|
||||
# Or using git
|
||||
git clone https://github.com/netresearch/netresearch-branding-skill.git ~/.claude/skills/netresearch-branding
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Automatic Activation
|
||||
|
||||
The skill activates automatically when working on:
|
||||
- Netresearch web projects
|
||||
- Brand implementation tasks
|
||||
- UI/UX development requiring brand compliance
|
||||
- Documentation needing brand guidelines
|
||||
|
||||
### Manual Invocation
|
||||
|
||||
```bash
|
||||
/skill netresearch-branding
|
||||
```
|
||||
|
||||
### Example Workflows
|
||||
|
||||
#### **Creating a New Landing Page**
|
||||
```
|
||||
User: "Create a landing page following Netresearch brand guidelines"
|
||||
|
||||
Claude: [Activates netresearch-branding skill]
|
||||
- Uses brand colors (Turquoise #2F99A4, Orange #FF4D00)
|
||||
- Applies Raleway for headlines, Open Sans for body
|
||||
- Implements high white space design principle
|
||||
- Creates responsive layout with brand components
|
||||
- Ensures WCAG AA accessibility compliance
|
||||
- Delivers production-ready HTML/CSS
|
||||
```
|
||||
|
||||
#### **Brand Compliance Check**
|
||||
```
|
||||
User: "Review this website design for Netresearch brand compliance"
|
||||
|
||||
Claude: [Activates netresearch-branding skill]
|
||||
- Verifies color usage (primary vs accent)
|
||||
- Checks typography (correct fonts and weights)
|
||||
- Validates spacing and white space principles
|
||||
- Reviews component implementations
|
||||
- Assesses accessibility compliance
|
||||
- Provides specific improvement recommendations
|
||||
```
|
||||
|
||||
#### **Component Development**
|
||||
```
|
||||
User: "Create a contact form with Netresearch branding"
|
||||
|
||||
Claude: [Activates netresearch-branding skill]
|
||||
- Uses branded form components
|
||||
- Applies correct input styling
|
||||
- Implements brand buttons
|
||||
- Ensures proper spacing
|
||||
- Validates accessibility
|
||||
- Provides complete HTML/CSS
|
||||
```
|
||||
|
||||
## Brand Quick Reference
|
||||
|
||||
### Colors
|
||||
|
||||
| Color | Hex | Usage |
|
||||
|-------|-----|-------|
|
||||
| **Turquoise** | `#2F99A4` | Primary brand color, links, CTAs |
|
||||
| **Orange** | `#FF4D00` | Accent color, emphasis only |
|
||||
| **Anthracite** | `#585961` | Primary text color |
|
||||
| **Light Grey** | `#CCCDCC` | Borders, backgrounds |
|
||||
| **White** | `#FFFFFF` | Primary background |
|
||||
|
||||
### Typography
|
||||
|
||||
| Element | Font | Size | Weight |
|
||||
|---------|------|------|--------|
|
||||
| **Headlines (Web)** | Raleway | 48px-56px | 700 |
|
||||
| **Body (Web)** | Open Sans | 16px | 400 |
|
||||
| **Buttons** | Raleway | 16px | 600 |
|
||||
| **Documents** | Calibri | 11-12pt | 400/700 |
|
||||
|
||||
### Key Principles
|
||||
|
||||
1. **High White Space** - Generous padding and margins throughout
|
||||
2. **Responsive Design** - Mobile-first approach with defined breakpoints
|
||||
3. **Accessibility** - WCAG AA compliance mandatory
|
||||
4. **Color Hierarchy** - Turquoise primary, orange accent only
|
||||
5. **Clean Typography** - Clear hierarchy with Raleway + Open Sans
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
netresearch-branding-skill/
|
||||
├── SKILL.md # Main skill file with guidelines
|
||||
├── README.md # This file
|
||||
├── references/
|
||||
│ ├── colors.md # Color palette reference
|
||||
│ ├── typography.md # Typography system
|
||||
│ └── web-design.md # Web components & patterns
|
||||
├── templates/
|
||||
│ ├── landing-page.html # Landing page template
|
||||
│ └── styles.css # Brand CSS framework
|
||||
└── examples/
|
||||
└── components.html # Interactive component showcase
|
||||
```
|
||||
|
||||
## Brand Component Library
|
||||
|
||||
### Buttons
|
||||
```html
|
||||
<!-- Primary action -->
|
||||
<button class="btn-primary">Get Started</button>
|
||||
|
||||
<!-- Secondary action -->
|
||||
<button class="btn-secondary">Learn More</button>
|
||||
|
||||
<!-- Tertiary action -->
|
||||
<button class="btn-outline">Contact Us</button>
|
||||
```
|
||||
|
||||
### Cards
|
||||
```html
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<h3 class="card-title">Card Title</h3>
|
||||
<p class="card-text">Card description.</p>
|
||||
<a href="#" class="link-standalone">Learn more</a>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Forms
|
||||
```html
|
||||
<div class="form-group">
|
||||
<label for="email" class="form-label">Email*</label>
|
||||
<input type="email" id="email" class="form-input"
|
||||
placeholder="your@email.com" required>
|
||||
</div>
|
||||
```
|
||||
|
||||
## CSS Custom Properties
|
||||
|
||||
The skill includes a complete set of CSS custom properties for easy implementation:
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* Brand Colors */
|
||||
--color-primary: #2F99A4;
|
||||
--color-accent: #FF4D00;
|
||||
--color-text-primary: #585961;
|
||||
|
||||
/* Typography */
|
||||
--font-headline: 'Raleway', sans-serif;
|
||||
--font-body: 'Open Sans', sans-serif;
|
||||
|
||||
/* Spacing */
|
||||
--spacing-xs: 8px;
|
||||
--spacing-sm: 12px;
|
||||
--spacing-md: 16px;
|
||||
--spacing-lg: 24px;
|
||||
--spacing-xl: 32px;
|
||||
--spacing-xxl: 48px;
|
||||
--spacing-3xl: 64px;
|
||||
--spacing-4xl: 96px;
|
||||
}
|
||||
```
|
||||
|
||||
## Accessibility Compliance
|
||||
|
||||
All brand implementations must meet **WCAG AA** standards:
|
||||
|
||||
✅ **Text Contrast Ratios:**
|
||||
- Anthracite on white: 8.3:1 (AAA)
|
||||
- White on turquoise: 4.5:1 (AA)
|
||||
- Turquoise on white: 3.8:1 (AA for large text)
|
||||
|
||||
✅ **Keyboard Navigation:**
|
||||
- All interactive elements accessible
|
||||
- Visible focus states
|
||||
- Logical tab order
|
||||
|
||||
✅ **Screen Readers:**
|
||||
- Semantic HTML
|
||||
- ARIA labels where needed
|
||||
- Alternative text for images
|
||||
|
||||
## Responsive Breakpoints
|
||||
|
||||
```css
|
||||
/* Mobile First Approach */
|
||||
/* Extra Small: 0-599px (default) */
|
||||
/* Small: 600px+ */
|
||||
/* Medium: 768px+ */
|
||||
/* Large: 1024px+ */
|
||||
/* Extra Large: 1440px+ */
|
||||
```
|
||||
|
||||
## Social Media Specifications
|
||||
|
||||
### Profile Images
|
||||
- **Facebook:** 180 × 180 px (displays at 170 × 170 px)
|
||||
- **LinkedIn:** 400 × 400 px (displays at 300 × 300 px)
|
||||
- **Twitter:** 400 × 400 px (displays at 200 × 200 px)
|
||||
- **XING:** 1024 × 1024 px
|
||||
|
||||
### Header/Cover Images
|
||||
- **Facebook:** 820 × 312 px
|
||||
- **LinkedIn (Company):** 1128 × 191 px
|
||||
- **Twitter:** 1500 × 500 px
|
||||
- **XING:** 970 × 250 px
|
||||
|
||||
## Quality Checklist
|
||||
|
||||
Before deploying any branded material:
|
||||
|
||||
**Visual Design:**
|
||||
- [ ] Colors match exact hex values
|
||||
- [ ] Turquoise is primary, orange is accent only
|
||||
- [ ] High white space maintained
|
||||
- [ ] Typography uses Raleway + Open Sans
|
||||
|
||||
**Technical:**
|
||||
- [ ] Responsive design implemented
|
||||
- [ ] WCAG AA compliance verified
|
||||
- [ ] Fonts loaded efficiently
|
||||
- [ ] CSS follows naming conventions
|
||||
|
||||
**Content:**
|
||||
- [ ] Logo used correctly
|
||||
- [ ] Images follow brand guidelines
|
||||
- [ ] Consistent tone and voice
|
||||
- [ ] No unapproved color combinations
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please follow these guidelines:
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch (`git checkout -b feature/improvement`)
|
||||
3. Make your changes
|
||||
4. Test thoroughly against brand guidelines
|
||||
5. Commit your changes (`git commit -m 'Add improvement'`)
|
||||
6. Push to the branch (`git push origin feature/improvement`)
|
||||
7. Create a Pull Request
|
||||
|
||||
## License
|
||||
|
||||
This skill is licensed under GPL-2.0-or-later, matching the TYPO3 project license.
|
||||
|
||||
## Support
|
||||
|
||||
**Issues and Questions:**
|
||||
- GitHub Issues: [Report issues](https://github.com/netresearch/netresearch-branding-skill/issues)
|
||||
|
||||
**Netresearch Contact:**
|
||||
- **Website:** https://www.netresearch.de
|
||||
- **Email:** info@netresearch.de
|
||||
- **Phone:** +49 341 49288-0
|
||||
- **Address:** Nonnenstraße 11d, 04229 Leipzig, Germany
|
||||
|
||||
## Related Skills
|
||||
|
||||
- **typo3-docs** - TYPO3 extension documentation creation
|
||||
- **typo3-testing** - TYPO3 extension testing infrastructure
|
||||
- **typo3-conformance** - TYPO3 coding standards evaluation
|
||||
|
||||
## Credits
|
||||
|
||||
Created by Netresearch DTT GmbH for consistent brand implementation across digital projects.
|
||||
|
||||
**Maintained By:** Netresearch DTT GmbH, Leipzig, Germany
|
||||
|
||||
---
|
||||
|
||||
**Version:** 1.0.0
|
||||
**Last Updated:** 2025-10-18
|
||||
1033
skills/netresearch-branding/SKILL.md
Normal file
1033
skills/netresearch-branding/SKILL.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg
|
||||
viewBox="-75 -75 440 440"
|
||||
zoomAndPan="disable"
|
||||
version="1.2"
|
||||
baseProfile="tiny-ps"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<title>Netresearch DTT GmbH</title>
|
||||
<g>
|
||||
<path
|
||||
fill="#2999a4"
|
||||
d="M209.6,0V31.62h32.77a26.38,26.38,0,0,1,26.44,26.43V242a26.38,26.38,0,0,1-26.44,26.44H209.6V300h47.93a42.77,42.77,0,0,0,42.86-42.86V42.89A42.76,42.76,0,0,0,257.53,0ZM43.25,0A42.76,42.76,0,0,0,.39,42.89V257.18A42.76,42.76,0,0,0,43.25,300H91.18V268.46H58.4A26.38,26.38,0,0,1,32,242v-184A26.37,26.37,0,0,1,58.4,31.62H91.18V0Z"
|
||||
transform="translate(-0.39 -0.04)" />
|
||||
<path
|
||||
fill="#595a62"
|
||||
d="M221.44,120.41c0-34.48-13.94-57.82-48.93-57.82-26.62,0-48.54,7.74-64.17,26.56l-.7-22.06-28.31.06V232.94h31.59V124.69c7.14-18.38,32.14-34.8,53-34.5,27.38.4,25.2,26.24,26,45.81v96.94h31.58"
|
||||
transform="translate(-0.39 -0.04)" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 921 B |
@@ -0,0 +1,54 @@
|
||||
# SKILL.md Refactoring Summary
|
||||
|
||||
**Date:** 2025-11-14
|
||||
**Version Change:** 1.0.0 → 1.1.0
|
||||
**Skill:** netresearch-branding
|
||||
|
||||
## Changes Applied
|
||||
|
||||
### Pattern 1: Removed "## Overview" Section
|
||||
- **Before:** Lines 14-23 contained "## Overview" section
|
||||
- **After:** Removed entire section
|
||||
- **Rationale:** Content duplicated YAML description; no unique value
|
||||
|
||||
### Pattern 2: Converted Do's/Don'ts to Imperative Form
|
||||
- **Before:** "Do's:" and "Don'ts:" bullet lists throughout
|
||||
- **After:** "When X" trigger-based imperative instructions
|
||||
|
||||
#### Color Application Rules
|
||||
- **Before:** Separate "Do's" and "Don'ts" lists
|
||||
- **After:** "When applying brand colors" with unified action-oriented instructions
|
||||
- Combined positive actions and avoidances into cohesive workflow
|
||||
|
||||
#### Typography Application Standards
|
||||
- **Before:** "Typography Do's and Don'ts" with separate lists
|
||||
- **After:** "When applying typography" with unified standards
|
||||
- Converted each guideline to action-oriented instruction
|
||||
|
||||
### Pattern 3: Converted "## Implementation Checklist" to Procedures
|
||||
- **Before:** Three checklists (Design Phase, Development Phase, Quality Assurance) with checkbox items
|
||||
- **After:** "## Pre-Launch Validation Procedures" with numbered procedural steps
|
||||
- **Changes:**
|
||||
- Design Phase → Design Phase Validation (6 numbered steps)
|
||||
- Development Phase → Development Phase Validation (8 numbered steps)
|
||||
- Quality Assurance → Quality Assurance Validation (8 numbered steps)
|
||||
- Brand Compliance Review → Brand Compliance Validation (3 check categories with numbered steps)
|
||||
|
||||
## Impact Analysis
|
||||
|
||||
**Readability:** Significantly improved - clear action-oriented instructions
|
||||
**Consistency:** Aligned with skill-creator best practices
|
||||
**Usability:** Enhanced - readers execute procedures rather than check boxes
|
||||
**Clarity:** Removed redundant overview; eliminated Do's/Don'ts duplication
|
||||
|
||||
## Files Modified
|
||||
|
||||
- `/SKILL.md` (lines 1-866)
|
||||
|
||||
## Verification
|
||||
|
||||
- Version number updated in YAML frontmatter: ✓
|
||||
- Overview section removed: ✓
|
||||
- All Do's/Don'ts converted to imperative: ✓
|
||||
- Checklists converted to procedures: ✓
|
||||
- No broken internal references: ✓
|
||||
313
skills/netresearch-branding/examples/components.html
Normal file
313
skills/netresearch-branding/examples/components.html
Normal file
@@ -0,0 +1,313 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Netresearch Brand Components</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Raleway:wght@400;600;700&family=Open+Sans:wght@400;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="../templates/styles.css">
|
||||
<style>
|
||||
.component-showcase {
|
||||
padding: 48px 24px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.component-section {
|
||||
margin-bottom: 64px;
|
||||
padding: 32px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
.component-section h2 {
|
||||
border-bottom: 3px solid #2F99A4;
|
||||
padding-bottom: 12px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.component-demo {
|
||||
margin: 24px 0;
|
||||
padding: 24px;
|
||||
background: #F5F5F5;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.code-block {
|
||||
background: #585961;
|
||||
color: #fff;
|
||||
padding: 16px;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="component-showcase">
|
||||
<h1 style="text-align: center; margin-bottom: 48px;">Netresearch Brand Components</h1>
|
||||
|
||||
<!-- Buttons -->
|
||||
<div class="component-section">
|
||||
<h2>Buttons</h2>
|
||||
|
||||
<div class="component-demo">
|
||||
<h3>Primary Buttons</h3>
|
||||
<button class="btn-primary btn-small">Small Button</button>
|
||||
<button class="btn-primary">Default Button</button>
|
||||
<button class="btn-primary btn-large">Large Button</button>
|
||||
</div>
|
||||
|
||||
<div class="component-demo">
|
||||
<h3>Secondary Buttons</h3>
|
||||
<button class="btn-secondary btn-small">Small Button</button>
|
||||
<button class="btn-secondary">Default Button</button>
|
||||
<button class="btn-secondary btn-large">Large Button</button>
|
||||
</div>
|
||||
|
||||
<div class="component-demo" style="background: #2F99A4; color: white;">
|
||||
<h3 style="color: white;">Outline Buttons (on dark background)</h3>
|
||||
<button class="btn-outline btn-small">Small Button</button>
|
||||
<button class="btn-outline">Default Button</button>
|
||||
<button class="btn-outline btn-large">Large Button</button>
|
||||
</div>
|
||||
|
||||
<div class="code-block">
|
||||
<button class="btn-primary">Primary Action</button>
|
||||
<button class="btn-secondary">Secondary Action</button>
|
||||
<button class="btn-outline">Tertiary Action</button>
|
||||
|
||||
<!-- Sizes -->
|
||||
<button class="btn-primary btn-small">Small</button>
|
||||
<button class="btn-primary">Default</button>
|
||||
<button class="btn-primary btn-large">Large</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cards -->
|
||||
<div class="component-section">
|
||||
<h2>Cards</h2>
|
||||
|
||||
<div class="component-demo">
|
||||
<div class="three-column">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<h3 class="card-title">Simple Card</h3>
|
||||
<p class="card-text">This is a basic card with title and text content.</p>
|
||||
<a href="#" class="link-standalone">Learn more</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<h3 class="card-title">Card with Footer</h3>
|
||||
<p class="card-text">This card includes a footer section with metadata.</p>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<span class="card-meta">Published: Jan 1, 2025</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<h3 class="card-title">Card with Button</h3>
|
||||
<p class="card-text">This card has a primary action button.</p>
|
||||
<button class="btn-primary">Take Action</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="code-block">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<h3 class="card-title">Card Title</h3>
|
||||
<p class="card-text">Card description.</p>
|
||||
<a href="#" class="link-standalone">Learn more</a>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<span class="card-meta">Metadata</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Typography -->
|
||||
<div class="component-section">
|
||||
<h2>Typography</h2>
|
||||
|
||||
<div class="component-demo">
|
||||
<h1>Heading 1 - Raleway Bold 48px</h1>
|
||||
<h2>Heading 2 - Raleway Bold 36px</h2>
|
||||
<h3>Heading 3 - Raleway SemiBold 28px</h3>
|
||||
<h4>Heading 4 - Raleway SemiBold 22px</h4>
|
||||
<p class="lead">Lead paragraph - Open Sans Regular 20px with comfortable line height for improved readability.</p>
|
||||
<p>Body text - Open Sans Regular 16px. This is the standard paragraph text used throughout the website. It maintains excellent readability with a line-height of 1.6.</p>
|
||||
<p><small>Small text - Open Sans Regular 14px for captions and metadata.</small></p>
|
||||
</div>
|
||||
|
||||
<div class="code-block">
|
||||
<h1>Heading 1</h1>
|
||||
<h2>Heading 2</h2>
|
||||
<h3>Heading 3</h3>
|
||||
<p class="lead">Lead paragraph</p>
|
||||
<p>Body text</p>
|
||||
<small>Small text</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Links -->
|
||||
<div class="component-section">
|
||||
<h2>Links</h2>
|
||||
|
||||
<div class="component-demo">
|
||||
<p>This is a paragraph with an <a href="#">inline link</a> within the text.</p>
|
||||
<p><a href="#" class="link-standalone">Standalone link with arrow</a></p>
|
||||
<p><a href="document.pdf" download>Download link with icon</a></p>
|
||||
<p><a href="https://example.com" target="_blank">External link with icon</a></p>
|
||||
</div>
|
||||
|
||||
<div class="code-block">
|
||||
<!-- Inline link -->
|
||||
<a href="#">Link text</a>
|
||||
|
||||
<!-- Standalone link with arrow -->
|
||||
<a href="#" class="link-standalone">Learn more</a>
|
||||
|
||||
<!-- Download link -->
|
||||
<a href="file.pdf" download>Download PDF</a>
|
||||
|
||||
<!-- External link -->
|
||||
<a href="https://example.com" target="_blank">External</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Forms -->
|
||||
<div class="component-section">
|
||||
<h2>Forms</h2>
|
||||
|
||||
<div class="component-demo">
|
||||
<form style="max-width: 600px;">
|
||||
<div class="form-group">
|
||||
<label for="demo-name" class="form-label">Name*</label>
|
||||
<input type="text" id="demo-name" class="form-input" placeholder="Your name" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="demo-email" class="form-label">Email*</label>
|
||||
<input type="email" id="demo-email" class="form-input" placeholder="your@email.com" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="demo-message" class="form-label">Message*</label>
|
||||
<textarea id="demo-message" class="form-textarea" placeholder="Your message" required></textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn-primary">Submit Form</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="code-block">
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label for="name" class="form-label">Name*</label>
|
||||
<input type="text" id="name" class="form-input"
|
||||
placeholder="Your name" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="message" class="form-label">Message*</label>
|
||||
<textarea id="message" class="form-textarea"
|
||||
placeholder="Your message" required></textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn-primary">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Color Palette -->
|
||||
<div class="component-section">
|
||||
<h2>Color Palette</h2>
|
||||
|
||||
<div class="component-demo">
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px;">
|
||||
<div style="text-align: center;">
|
||||
<div style="background: #2F99A4; height: 100px; border-radius: 8px; margin-bottom: 8px;"></div>
|
||||
<strong>Turquoise Primary</strong><br>
|
||||
<code>#2F99A4</code>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<div style="background: #FF4D00; height: 100px; border-radius: 8px; margin-bottom: 8px;"></div>
|
||||
<strong>Orange Accent</strong><br>
|
||||
<code>#FF4D00</code>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<div style="background: #585961; height: 100px; border-radius: 8px; margin-bottom: 8px;"></div>
|
||||
<strong>Anthracite Text</strong><br>
|
||||
<code>#585961</code>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<div style="background: #CCCDCC; height: 100px; border-radius: 8px; margin-bottom: 8px;"></div>
|
||||
<strong>Light Grey</strong><br>
|
||||
<code>#CCCDCC</code>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<div style="background: #FFFFFF; height: 100px; border-radius: 8px; margin-bottom: 8px; border: 1px solid #E5E5E5;"></div>
|
||||
<strong>White</strong><br>
|
||||
<code>#FFFFFF</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="code-block">
|
||||
/* CSS Custom Properties */
|
||||
:root {
|
||||
--color-primary: #2F99A4;
|
||||
--color-accent: #FF4D00;
|
||||
--color-text-primary: #585961;
|
||||
--color-border: #CCCDCC;
|
||||
--color-background: #FFFFFF;
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Grid Layouts -->
|
||||
<div class="component-section">
|
||||
<h2>Grid Layouts</h2>
|
||||
|
||||
<div class="component-demo">
|
||||
<h3>Two-Column Layout</h3>
|
||||
<div class="two-column" style="margin-bottom: 32px;">
|
||||
<div style="background: #F5F5F5; padding: 24px; border-radius: 4px;">Column 1</div>
|
||||
<div style="background: #F5F5F5; padding: 24px; border-radius: 4px;">Column 2</div>
|
||||
</div>
|
||||
|
||||
<h3>Three-Column Layout</h3>
|
||||
<div class="three-column">
|
||||
<div style="background: #F5F5F5; padding: 24px; border-radius: 4px;">Column 1</div>
|
||||
<div style="background: #F5F5F5; padding: 24px; border-radius: 4px;">Column 2</div>
|
||||
<div style="background: #F5F5F5; padding: 24px; border-radius: 4px;">Column 3</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="code-block">
|
||||
<!-- Two-column layout -->
|
||||
<div class="two-column">
|
||||
<div>Content 1</div>
|
||||
<div>Content 2</div>
|
||||
</div>
|
||||
|
||||
<!-- Three-column layout -->
|
||||
<div class="three-column">
|
||||
<div>Content 1</div>
|
||||
<div>Content 2</div>
|
||||
<div>Content 3</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
347
skills/netresearch-branding/references/colors.md
Normal file
347
skills/netresearch-branding/references/colors.md
Normal file
@@ -0,0 +1,347 @@
|
||||
# Netresearch Brand Colors Reference
|
||||
|
||||
Complete color palette with technical specifications and usage guidelines.
|
||||
|
||||
## Primary Brand Colors
|
||||
|
||||
### Turquoise (Primary)
|
||||
**Technical Specifications:**
|
||||
- **Hex:** `#2F99A4`
|
||||
- **RGB:** `rgb(47, 153, 164)`
|
||||
- **CMYK:** `C71 M7 Y17 K36`
|
||||
- **Pantone:** 7709 C (approximate)
|
||||
- **NCS:** S 3040-B30G
|
||||
|
||||
**Usage:**
|
||||
- Primary brand color for all digital and print materials
|
||||
- Main CTA buttons and interactive elements
|
||||
- Headers and key visual elements
|
||||
- Links in default state
|
||||
- Primary navigation highlights
|
||||
- Brand identity elements
|
||||
|
||||
**Contrast Ratios (WCAG AA):**
|
||||
- On white background: 3.8:1 (Meets AA for large text)
|
||||
- On #F5F5F5: 4.2:1 (Meets AA for large text)
|
||||
- White text on turquoise: 4.5:1 (Meets AA for all text)
|
||||
|
||||
**Web Implementation:**
|
||||
```css
|
||||
:root {
|
||||
--color-primary: #2F99A4;
|
||||
--color-primary-rgb: 47, 153, 164;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--color-primary);
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.link-primary {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Orange (Accent)
|
||||
**Technical Specifications:**
|
||||
- **Hex:** `#FF4D00`
|
||||
- **RGB:** `rgb(255, 77, 0)`
|
||||
- **CMYK:** `C0 M70 Y100 K0`
|
||||
- **Pantone:** 172 C (approximate)
|
||||
- **NCS:** S 1080-Y60R
|
||||
|
||||
**Usage:**
|
||||
- Accent color for emphasis and highlights
|
||||
- Secondary CTA buttons
|
||||
- Important notifications and alerts
|
||||
- Hover states on primary elements
|
||||
- Call-out boxes and highlights
|
||||
- Limited use - accent only, not dominant
|
||||
|
||||
**Contrast Ratios (WCAG AA):**
|
||||
- On white background: 3.9:1 (Meets AA for large text)
|
||||
- On #F5F5F5: 4.3:1 (Meets AA for large text)
|
||||
- White text on orange: 3.2:1 (Meets AA for large text only)
|
||||
|
||||
**Web Implementation:**
|
||||
```css
|
||||
:root {
|
||||
--color-accent: #FF4D00;
|
||||
--color-accent-rgb: 255, 77, 0;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: var(--color-accent);
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
border-left: 4px solid var(--color-accent);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Neutral Colors
|
||||
|
||||
### Anthracite (Text Primary)
|
||||
**Technical Specifications:**
|
||||
- **Hex:** `#585961`
|
||||
- **RGB:** `rgb(88, 89, 97)`
|
||||
- **CMYK:** C9 M2 Y0 K62`
|
||||
- **NCS:** S 5502-B
|
||||
|
||||
**Usage:**
|
||||
- Primary text color for body copy
|
||||
- Headlines and subheadings
|
||||
- Navigation text
|
||||
- Form labels
|
||||
- Default icon color
|
||||
|
||||
**Contrast Ratios (WCAG AA):**
|
||||
- On white background: 8.3:1 (Meets AAA for all text)
|
||||
- On #F5F5F5: 7.8:1 (Meets AAA for all text)
|
||||
|
||||
**Web Implementation:**
|
||||
```css
|
||||
:root {
|
||||
--color-text-primary: #585961;
|
||||
}
|
||||
|
||||
body, p, h1, h2, h3 {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Light Grey (Backgrounds)
|
||||
**Technical Specifications:**
|
||||
- **Hex:** `#CCCDCC`
|
||||
- **RGB:** `rgb(204, 205, 204)`
|
||||
- **CMYK:** C0 M0 Y0 K20`
|
||||
- **NCS:** S 2000-N
|
||||
|
||||
**Usage:**
|
||||
- Background sections and cards
|
||||
- Subtle borders and dividers
|
||||
- Disabled states
|
||||
- Alternating table rows
|
||||
- Input field borders
|
||||
|
||||
**Contrast Note:**
|
||||
- Not suitable for text on white background
|
||||
- Use for backgrounds and UI elements only
|
||||
|
||||
**Web Implementation:**
|
||||
```css
|
||||
:root {
|
||||
--color-background-light: #CCCDCC;
|
||||
--color-border-light: #CCCDCC;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #F5F5F5;
|
||||
border: 1px solid var(--color-border-light);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### White (Background)
|
||||
**Technical Specifications:**
|
||||
- **Hex:** `#FFFFFF`
|
||||
- **RGB:** `rgb(255, 255, 255)`
|
||||
|
||||
**Usage:**
|
||||
- Primary background color
|
||||
- Text on dark backgrounds
|
||||
- Card backgrounds
|
||||
- Clean, spacious layouts
|
||||
|
||||
---
|
||||
|
||||
## Color Combinations
|
||||
|
||||
### Approved Combinations
|
||||
|
||||
**Primary Palette:**
|
||||
```css
|
||||
/* Turquoise on white - Primary brand */
|
||||
.combination-1 {
|
||||
background: #FFFFFF;
|
||||
color: #2F99A4;
|
||||
}
|
||||
|
||||
/* White text on turquoise - High impact */
|
||||
.combination-2 {
|
||||
background: #2F99A4;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
/* Orange accent with turquoise */
|
||||
.combination-3 {
|
||||
background: #2F99A4;
|
||||
border-color: #FF4D00;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
```
|
||||
|
||||
**Text Combinations:**
|
||||
```css
|
||||
/* Body text on white - Standard */
|
||||
.text-standard {
|
||||
background: #FFFFFF;
|
||||
color: #585961;
|
||||
}
|
||||
|
||||
/* Body text on light grey - Sections */
|
||||
.text-section {
|
||||
background: #F5F5F5;
|
||||
color: #585961;
|
||||
}
|
||||
```
|
||||
|
||||
### Avoid These Combinations
|
||||
|
||||
❌ **Never Use:**
|
||||
- Orange on turquoise (poor contrast)
|
||||
- Light grey text on white (fails WCAG)
|
||||
- Turquoise text smaller than 18px on white (AA compliance)
|
||||
- Orange text on white for small text (fails AA)
|
||||
|
||||
---
|
||||
|
||||
## Accessibility Guidelines
|
||||
|
||||
### WCAG AA Compliance
|
||||
|
||||
**Minimum Contrast Ratios:**
|
||||
- **Normal text (< 18px):** 4.5:1
|
||||
- **Large text (≥ 18px):** 3:1
|
||||
- **UI components:** 3:1
|
||||
|
||||
**Approved Text Combinations:**
|
||||
|
||||
✅ **Pass AA for All Text Sizes:**
|
||||
- Anthracite (#585961) on white: 8.3:1 ✓
|
||||
- Anthracite (#585961) on #F5F5F5: 7.8:1 ✓
|
||||
- White on turquoise (#2F99A4): 4.5:1 ✓
|
||||
|
||||
✅ **Pass AA for Large Text Only:**
|
||||
- Turquoise (#2F99A4) on white: 3.8:1 ✓
|
||||
- Orange (#FF4D00) on white: 3.9:1 ✓
|
||||
- White on orange (#FF4D00): 3.2:1 ✓
|
||||
|
||||
❌ **Fail AA (Do Not Use):**
|
||||
- Light grey (#CCCDCC) on white: 1.6:1 ✗
|
||||
|
||||
---
|
||||
|
||||
## Usage Guidelines by Context
|
||||
|
||||
### Websites
|
||||
- **Primary color:** Turquoise for headers, links, CTAs
|
||||
- **Accent color:** Orange sparingly for emphasis
|
||||
- **Background:** White (#FFFFFF) or subtle off-white (#F5F5F5)
|
||||
- **Text:** Anthracite (#585961)
|
||||
- **High white space:** Let colors breathe
|
||||
|
||||
### Social Media
|
||||
- **Header images:** Turquoise primary with orange accents
|
||||
- **Profile elements:** Turquoise logo on white
|
||||
- **Post graphics:** Maximum 2 brand colors per graphic
|
||||
|
||||
### Documents
|
||||
- **Headers:** Turquoise or anthracite
|
||||
- **Highlights:** Orange for key information
|
||||
- **Body text:** Anthracite
|
||||
- **Backgrounds:** White primary, light grey for emphasis
|
||||
|
||||
### Presentations
|
||||
- **Title slides:** Turquoise background with white text
|
||||
- **Content slides:** White background with turquoise accents
|
||||
- **Emphasis:** Orange for key points
|
||||
|
||||
---
|
||||
|
||||
## CSS Custom Properties (Complete Set)
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* Primary Colors */
|
||||
--color-primary: #2F99A4;
|
||||
--color-primary-rgb: 47, 153, 164;
|
||||
--color-primary-light: rgba(47, 153, 164, 0.1);
|
||||
--color-primary-dark: #257880;
|
||||
|
||||
/* Accent Colors */
|
||||
--color-accent: #FF4D00;
|
||||
--color-accent-rgb: 255, 77, 0;
|
||||
--color-accent-light: rgba(255, 77, 0, 0.1);
|
||||
--color-accent-dark: #CC3D00;
|
||||
|
||||
/* Neutral Colors */
|
||||
--color-text-primary: #585961;
|
||||
--color-text-secondary: #8A8B93;
|
||||
--color-background: #FFFFFF;
|
||||
--color-background-alt: #F5F5F5;
|
||||
--color-border: #CCCDCC;
|
||||
--color-border-light: #E5E5E5;
|
||||
|
||||
/* Semantic Colors */
|
||||
--color-success: #28A745;
|
||||
--color-warning: #FFC107;
|
||||
--color-error: #DC3545;
|
||||
--color-info: var(--color-primary);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Sass/SCSS Variables
|
||||
|
||||
```scss
|
||||
// Primary Colors
|
||||
$color-primary: #2F99A4;
|
||||
$color-accent: #FF4D00;
|
||||
|
||||
// Neutral Colors
|
||||
$color-anthracite: #585961;
|
||||
$color-light-grey: #CCCDCC;
|
||||
$color-white: #FFFFFF;
|
||||
|
||||
// Backgrounds
|
||||
$color-bg-primary: $color-white;
|
||||
$color-bg-secondary: #F5F5F5;
|
||||
|
||||
// Text
|
||||
$color-text-primary: $color-anthracite;
|
||||
$color-text-secondary: lighten($color-anthracite, 15%);
|
||||
|
||||
// Borders
|
||||
$color-border: $color-light-grey;
|
||||
$color-border-light: #E5E5E5;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Color Testing Checklist
|
||||
|
||||
Before deploying any brand materials:
|
||||
|
||||
- [ ] All text meets WCAG AA contrast requirements
|
||||
- [ ] Turquoise is primary, orange is accent only
|
||||
- [ ] No unapproved color combinations used
|
||||
- [ ] Colors match exact hex values
|
||||
- [ ] High white space maintained
|
||||
- [ ] Tested on multiple displays for color accuracy
|
||||
- [ ] Print materials use CMYK values
|
||||
- [ ] Digital materials use RGB/Hex values
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2025-10-18*
|
||||
*Maintained by: Netresearch DTT GmbH*
|
||||
609
skills/netresearch-branding/references/typography.md
Normal file
609
skills/netresearch-branding/references/typography.md
Normal file
@@ -0,0 +1,609 @@
|
||||
# Netresearch Typography Reference
|
||||
|
||||
Complete typography system with font specifications, hierarchy, and implementation guidelines.
|
||||
|
||||
## Font Family Overview
|
||||
|
||||
| Context | Typeface | Purpose |
|
||||
|---------|----------|---------|
|
||||
| Web Headlines | **Raleway** | Digital headers, navigation, buttons |
|
||||
| Web Body Text | **Open Sans** | Digital body copy, captions, lists |
|
||||
| Documents | **Calibri** | Word, PowerPoint, Excel, email |
|
||||
| Print | **Raleway + Open Sans** | Brochures, flyers, business cards |
|
||||
|
||||
---
|
||||
|
||||
## Raleway (Headlines & Display)
|
||||
|
||||
### Font Specifications
|
||||
|
||||
**Family:** Raleway
|
||||
**Designer:** Matt McInerney, Pablo Impallari, Rodrigo Fuenzalida
|
||||
**License:** Open Font License (free for commercial use)
|
||||
**Classification:** Sans-serif, Geometric
|
||||
|
||||
**Recommended Weights for Netresearch:**
|
||||
- **Regular (400)** - Secondary headlines, navigation
|
||||
- **Semi-Bold (600)** - Buttons, emphasized text
|
||||
- **Bold (700)** - Primary headlines, h1-h2
|
||||
|
||||
### Web Font Loading
|
||||
|
||||
**Google Fonts CDN:**
|
||||
```html
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Raleway:wght@400;600;700&display=swap" rel="stylesheet">
|
||||
```
|
||||
|
||||
**CSS @import:**
|
||||
```css
|
||||
@import url('https://fonts.googleapis.com/css2?family=Raleway:wght@400;600;700&display=swap');
|
||||
```
|
||||
|
||||
**Self-Hosted (Recommended for Performance):**
|
||||
```css
|
||||
@font-face {
|
||||
font-family: 'Raleway';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('/fonts/raleway-regular.woff2') format('woff2'),
|
||||
url('/fonts/raleway-regular.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Raleway';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url('/fonts/raleway-semibold.woff2') format('woff2'),
|
||||
url('/fonts/raleway-semibold.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Raleway';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('/fonts/raleway-bold.woff2') format('woff2'),
|
||||
url('/fonts/raleway-bold.woff') format('woff');
|
||||
}
|
||||
```
|
||||
|
||||
### Usage Guidelines
|
||||
|
||||
**Use Raleway For:**
|
||||
- ✅ Page headlines (h1, h2, h3)
|
||||
- ✅ Navigation menu items
|
||||
- ✅ Button labels
|
||||
- ✅ Section headers
|
||||
- ✅ Pull quotes and callouts
|
||||
- ✅ Hero text and taglines
|
||||
|
||||
**Do Not Use Raleway For:**
|
||||
- ❌ Body paragraphs (use Open Sans)
|
||||
- ❌ Long-form content (readability issues)
|
||||
- ❌ Small text below 14px (legibility)
|
||||
- ❌ Dense information tables
|
||||
|
||||
---
|
||||
|
||||
## Open Sans (Body Text)
|
||||
|
||||
### Font Specifications
|
||||
|
||||
**Family:** Open Sans
|
||||
**Designer:** Steve Matteson
|
||||
**License:** Apache License 2.0 (free for commercial use)
|
||||
**Classification:** Sans-serif, Humanist
|
||||
|
||||
**Recommended Weights for Netresearch:**
|
||||
- **Regular (400)** - Body text, lists, captions
|
||||
- **Semi-Bold (600)** - Emphasized body text, labels
|
||||
- **Bold (700)** - Strong emphasis, sub-headers
|
||||
|
||||
### Web Font Loading
|
||||
|
||||
**Google Fonts CDN:**
|
||||
```html
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600;700&display=swap" rel="stylesheet">
|
||||
```
|
||||
|
||||
**CSS @import:**
|
||||
```css
|
||||
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600;700&display=swap');
|
||||
```
|
||||
|
||||
**Self-Hosted:**
|
||||
```css
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('/fonts/opensans-regular.woff2') format('woff2'),
|
||||
url('/fonts/opensans-regular.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url('/fonts/opensans-semibold.woff2') format('woff2'),
|
||||
url('/fonts/opensans-semibold.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('/fonts/opensans-bold.woff2') format('woff2'),
|
||||
url('/fonts/opensans-bold.woff') format('woff');
|
||||
}
|
||||
```
|
||||
|
||||
### Usage Guidelines
|
||||
|
||||
**Use Open Sans For:**
|
||||
- ✅ All body copy and paragraphs
|
||||
- ✅ Lists (ordered and unordered)
|
||||
- ✅ Form input text
|
||||
- ✅ Captions and metadata
|
||||
- ✅ Table content
|
||||
- ✅ Footer text
|
||||
|
||||
**Do Not Use Open Sans For:**
|
||||
- ❌ Main page headlines (use Raleway)
|
||||
- ❌ Large display text above 48px (use Raleway)
|
||||
|
||||
---
|
||||
|
||||
## Calibri (Documents)
|
||||
|
||||
### Font Specifications
|
||||
|
||||
**Family:** Calibri
|
||||
**Designer:** Luc(as) de Groot
|
||||
**License:** Microsoft bundled (available on Windows/Office)
|
||||
**Classification:** Sans-serif, Humanist
|
||||
|
||||
**Weights Available:**
|
||||
- Regular (400)
|
||||
- Bold (700)
|
||||
- Italic (400)
|
||||
- Bold Italic (700)
|
||||
|
||||
### Usage Guidelines
|
||||
|
||||
**Use Calibri For:**
|
||||
- ✅ Microsoft Word documents
|
||||
- ✅ PowerPoint presentations
|
||||
- ✅ Excel spreadsheets
|
||||
- ✅ Outlook emails
|
||||
- ✅ Internal documentation
|
||||
|
||||
**Fallback for Non-Microsoft Platforms:**
|
||||
```css
|
||||
font-family: Calibri, 'Open Sans', Arial, sans-serif;
|
||||
```
|
||||
|
||||
**Document Templates:**
|
||||
- **Headings:** Calibri Bold, Turquoise (#2F99A4)
|
||||
- **Body Text:** Calibri Regular, Anthracite (#585961)
|
||||
- **Emphasis:** Calibri Bold or Calibri Italic
|
||||
|
||||
---
|
||||
|
||||
## Typography Scale
|
||||
|
||||
### Web Typography Hierarchy
|
||||
|
||||
```css
|
||||
/* h1 - Page Title */
|
||||
h1 {
|
||||
font-family: 'Raleway', sans-serif;
|
||||
font-size: 48px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
color: #585961;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
/* h2 - Section Header */
|
||||
h2 {
|
||||
font-family: 'Raleway', sans-serif;
|
||||
font-size: 36px;
|
||||
font-weight: 700;
|
||||
line-height: 1.3;
|
||||
color: #585961;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* h3 - Subsection Header */
|
||||
h3 {
|
||||
font-family: 'Raleway', sans-serif;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
color: #585961;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* h4 - Minor Header */
|
||||
h4 {
|
||||
font-family: 'Raleway', sans-serif;
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
color: #585961;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
/* h5 - Small Header */
|
||||
h5 {
|
||||
font-family: 'Raleway', sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
color: #585961;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
/* Body Text */
|
||||
body, p {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 1.6;
|
||||
color: #585961;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* Small Text / Captions */
|
||||
.text-small, small {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 1.6;
|
||||
color: #585961;
|
||||
}
|
||||
|
||||
/* Lead Paragraph */
|
||||
.lead {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
line-height: 1.6;
|
||||
color: #585961;
|
||||
}
|
||||
```
|
||||
|
||||
### Responsive Typography
|
||||
|
||||
```css
|
||||
/* Mobile First Approach */
|
||||
|
||||
/* Base (Mobile) */
|
||||
h1 { font-size: 32px; }
|
||||
h2 { font-size: 26px; }
|
||||
h3 { font-size: 22px; }
|
||||
h4 { font-size: 18px; }
|
||||
body, p { font-size: 16px; }
|
||||
|
||||
/* Tablet (768px+) */
|
||||
@media (min-width: 768px) {
|
||||
h1 { font-size: 40px; }
|
||||
h2 { font-size: 32px; }
|
||||
h3 { font-size: 26px; }
|
||||
h4 { font-size: 20px; }
|
||||
}
|
||||
|
||||
/* Desktop (1024px+) */
|
||||
@media (min-width: 1024px) {
|
||||
h1 { font-size: 48px; }
|
||||
h2 { font-size: 36px; }
|
||||
h3 { font-size: 28px; }
|
||||
h4 { font-size: 22px; }
|
||||
}
|
||||
|
||||
/* Large Desktop (1440px+) */
|
||||
@media (min-width: 1440px) {
|
||||
h1 { font-size: 56px; }
|
||||
h2 { font-size: 40px; }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Line Height & Spacing
|
||||
|
||||
### Line Height Guidelines
|
||||
|
||||
```css
|
||||
/* Headlines - Tighter Leading */
|
||||
h1, h2, h3 {
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
h4, h5, h6 {
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* Body Text - Comfortable Reading */
|
||||
p, li, td {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Small Text */
|
||||
small, .caption {
|
||||
line-height: 1.5;
|
||||
}
|
||||
```
|
||||
|
||||
### Vertical Rhythm
|
||||
|
||||
```css
|
||||
/* Consistent spacing system */
|
||||
:root {
|
||||
--spacing-xs: 8px;
|
||||
--spacing-sm: 12px;
|
||||
--spacing-md: 16px;
|
||||
--spacing-lg: 24px;
|
||||
--spacing-xl: 32px;
|
||||
--spacing-xxl: 48px;
|
||||
}
|
||||
|
||||
/* Apply to typography */
|
||||
h1 { margin-bottom: var(--spacing-lg); }
|
||||
h2 { margin-bottom: var(--spacing-lg); }
|
||||
h3 { margin-bottom: var(--spacing-md); }
|
||||
h4, h5 { margin-bottom: var(--spacing-sm); }
|
||||
p { margin-bottom: var(--spacing-md); }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Special Typography Elements
|
||||
|
||||
### Buttons
|
||||
|
||||
```css
|
||||
.btn {
|
||||
font-family: 'Raleway', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
letter-spacing: 0.5px;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.btn-large {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.btn-small {
|
||||
font-size: 14px;
|
||||
}
|
||||
```
|
||||
|
||||
### Navigation
|
||||
|
||||
```css
|
||||
.nav-link {
|
||||
font-family: 'Raleway', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.nav-link:hover,
|
||||
.nav-link.active {
|
||||
font-weight: 600;
|
||||
}
|
||||
```
|
||||
|
||||
### Blockquotes / Pull Quotes
|
||||
|
||||
```css
|
||||
blockquote {
|
||||
font-family: 'Raleway', sans-serif;
|
||||
font-size: 24px;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
font-style: italic;
|
||||
color: #2F99A4;
|
||||
border-left: 4px solid #FF4D00;
|
||||
padding-left: 24px;
|
||||
margin: 32px 0;
|
||||
}
|
||||
|
||||
blockquote cite {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
color: #585961;
|
||||
display: block;
|
||||
margin-top: 12px;
|
||||
}
|
||||
```
|
||||
|
||||
### Lists
|
||||
|
||||
```css
|
||||
ul, ol {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 16px;
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* Custom bullet points using brand color */
|
||||
ul.branded {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
ul.branded li::before {
|
||||
content: '●';
|
||||
color: #2F99A4;
|
||||
font-weight: bold;
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
margin-left: -1em;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Font Loading Strategy
|
||||
|
||||
### Performance Optimization
|
||||
|
||||
```html
|
||||
<!-- Critical CSS with system font fallback -->
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont,
|
||||
'Segoe UI', sans-serif;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Preload critical fonts -->
|
||||
<link rel="preload"
|
||||
href="/fonts/raleway-bold.woff2"
|
||||
as="font"
|
||||
type="font/woff2"
|
||||
crossorigin>
|
||||
<link rel="preload"
|
||||
href="/fonts/opensans-regular.woff2"
|
||||
as="font"
|
||||
type="font/woff2"
|
||||
crossorigin>
|
||||
```
|
||||
|
||||
### Font Display Strategy
|
||||
|
||||
```css
|
||||
@font-face {
|
||||
font-family: 'Raleway';
|
||||
font-display: swap; /* Show fallback immediately, swap when loaded */
|
||||
/* ... other properties */
|
||||
}
|
||||
```
|
||||
|
||||
### Fallback Stack
|
||||
|
||||
```css
|
||||
/* Complete fallback chain */
|
||||
--font-headlines: 'Raleway', 'Helvetica Neue', Arial, sans-serif;
|
||||
--font-body: 'Open Sans', -apple-system, BlinkMacSystemFont,
|
||||
'Segoe UI', Roboto, sans-serif;
|
||||
--font-documents: Calibri, 'Open Sans', Arial, sans-serif;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Accessibility Considerations
|
||||
|
||||
### Minimum Font Sizes
|
||||
|
||||
- **Body text:** Never below 16px (1rem)
|
||||
- **Small text:** Never below 14px (0.875rem)
|
||||
- **Buttons:** Minimum 16px for touch targets
|
||||
|
||||
### Contrast Requirements
|
||||
|
||||
All text must meet WCAG AA:
|
||||
- **Normal text:** 4.5:1 contrast ratio
|
||||
- **Large text (≥18px):** 3:1 contrast ratio
|
||||
|
||||
### Readability
|
||||
|
||||
**Line Length:**
|
||||
- Optimal: 50-75 characters per line
|
||||
- Maximum: 90 characters per line
|
||||
|
||||
```css
|
||||
.content {
|
||||
max-width: 75ch; /* Character-based width */
|
||||
}
|
||||
```
|
||||
|
||||
**Paragraph Spacing:**
|
||||
```css
|
||||
p + p {
|
||||
margin-top: 16px; /* Clear paragraph separation */
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CSS Custom Properties (Complete Set)
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* Font Families */
|
||||
--font-headline: 'Raleway', sans-serif;
|
||||
--font-body: 'Open Sans', sans-serif;
|
||||
--font-document: Calibri, 'Open Sans', sans-serif;
|
||||
|
||||
/* Font Sizes */
|
||||
--font-size-xs: 12px;
|
||||
--font-size-sm: 14px;
|
||||
--font-size-base: 16px;
|
||||
--font-size-lg: 18px;
|
||||
--font-size-xl: 20px;
|
||||
--font-size-2xl: 24px;
|
||||
--font-size-3xl: 28px;
|
||||
--font-size-4xl: 36px;
|
||||
--font-size-5xl: 48px;
|
||||
--font-size-6xl: 56px;
|
||||
|
||||
/* Font Weights */
|
||||
--font-weight-normal: 400;
|
||||
--font-weight-semibold: 600;
|
||||
--font-weight-bold: 700;
|
||||
|
||||
/* Line Heights */
|
||||
--line-height-tight: 1.2;
|
||||
--line-height-snug: 1.4;
|
||||
--line-height-normal: 1.6;
|
||||
--line-height-relaxed: 1.8;
|
||||
|
||||
/* Letter Spacing */
|
||||
--letter-spacing-tight: -0.5px;
|
||||
--letter-spacing-normal: 0;
|
||||
--letter-spacing-wide: 0.5px;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Typography Testing Checklist
|
||||
|
||||
Before deploying:
|
||||
|
||||
- [ ] Fonts load correctly on all major browsers
|
||||
- [ ] Fallback fonts provide acceptable experience
|
||||
- [ ] All text meets WCAG AA contrast requirements
|
||||
- [ ] Font files optimized (woff2 format preferred)
|
||||
- [ ] Font-display: swap implemented
|
||||
- [ ] Line lengths within 50-90 character range
|
||||
- [ ] Responsive typography scales appropriately
|
||||
- [ ] No text smaller than 14px in production
|
||||
- [ ] Headlines use Raleway, body uses Open Sans
|
||||
- [ ] Documents use Calibri with proper fallbacks
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2025-10-18*
|
||||
*Maintained by: Netresearch DTT GmbH*
|
||||
860
skills/netresearch-branding/references/web-design.md
Normal file
860
skills/netresearch-branding/references/web-design.md
Normal file
@@ -0,0 +1,860 @@
|
||||
# Netresearch Web Design Guidelines
|
||||
|
||||
Comprehensive web design patterns, layouts, components, and responsive design standards.
|
||||
|
||||
## Core Design Principles
|
||||
|
||||
### 1. High White Space
|
||||
**Philosophy:** Clean, uncluttered layouts that prioritize content and readability.
|
||||
|
||||
```css
|
||||
/* Generous spacing throughout */
|
||||
:root {
|
||||
--spacing-xs: 8px;
|
||||
--spacing-sm: 12px;
|
||||
--spacing-md: 16px;
|
||||
--spacing-lg: 24px;
|
||||
--spacing-xl: 32px;
|
||||
--spacing-xxl: 48px;
|
||||
--spacing-3xl: 64px;
|
||||
--spacing-4xl: 96px;
|
||||
}
|
||||
|
||||
/* Section spacing */
|
||||
section {
|
||||
padding: var(--spacing-4xl) 0;
|
||||
}
|
||||
|
||||
/* Content spacing */
|
||||
.content {
|
||||
padding: var(--spacing-xxl);
|
||||
}
|
||||
```
|
||||
|
||||
**Guidelines:**
|
||||
- Minimum 48px padding around major sections
|
||||
- Minimum 24px margin between content blocks
|
||||
- Generous line-height (1.6) for readability
|
||||
- Never crowd elements - let designs breathe
|
||||
|
||||
---
|
||||
|
||||
### 2. Responsive Grid System
|
||||
|
||||
**12-Column Flexible Grid:**
|
||||
```css
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12, 1fr);
|
||||
gap: var(--spacing-lg);
|
||||
padding: 0 var(--spacing-lg);
|
||||
}
|
||||
|
||||
/* Content within container */
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 var(--spacing-lg);
|
||||
}
|
||||
|
||||
/* Responsive columns */
|
||||
.col-12 { grid-column: span 12; }
|
||||
.col-6 { grid-column: span 6; }
|
||||
.col-4 { grid-column: span 4; }
|
||||
.col-3 { grid-column: span 3; }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.col-6, .col-4, .col-3 {
|
||||
grid-column: span 12;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Design Hierarchy
|
||||
- **Primary:** Turquoise (#2F99A4) for key elements
|
||||
- **Secondary:** Orange (#FF4D00) for accents only
|
||||
- **Typography:** Clear hierarchy with Raleway headers and Open Sans body
|
||||
- **Visual Flow:** Top to bottom, left to right reading patterns
|
||||
|
||||
---
|
||||
|
||||
## Responsive Breakpoints
|
||||
|
||||
### Standard Breakpoints
|
||||
|
||||
```css
|
||||
/* Mobile First Approach */
|
||||
|
||||
/* Extra Small (Mobile Portrait) */
|
||||
/* Default: 0px - 599px */
|
||||
|
||||
/* Small (Mobile Landscape) */
|
||||
@media (min-width: 600px) {
|
||||
/* Adjustments for landscape phones */
|
||||
}
|
||||
|
||||
/* Medium (Tablet) */
|
||||
@media (min-width: 768px) {
|
||||
/* Two-column layouts start here */
|
||||
}
|
||||
|
||||
/* Large (Desktop) */
|
||||
@media (min-width: 1024px) {
|
||||
/* Full desktop layouts */
|
||||
}
|
||||
|
||||
/* Extra Large (Wide Desktop) */
|
||||
@media (min-width: 1440px) {
|
||||
/* Maximum content width, larger typography */
|
||||
}
|
||||
|
||||
/* Ultra Wide */
|
||||
@media (min-width: 1920px) {
|
||||
/* Optional: Enhanced spacing for ultra-wide displays */
|
||||
}
|
||||
```
|
||||
|
||||
### Container Widths
|
||||
|
||||
```css
|
||||
.container {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
@media (min-width: 600px) {
|
||||
.container { max-width: 580px; }
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container { max-width: 750px; }
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.container { max-width: 1000px; }
|
||||
}
|
||||
|
||||
@media (min-width: 1440px) {
|
||||
.container { max-width: 1200px; }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Component Library
|
||||
|
||||
### Buttons
|
||||
|
||||
```css
|
||||
/* Primary Button */
|
||||
.btn-primary {
|
||||
display: inline-block;
|
||||
font-family: 'Raleway', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
padding: 12px 32px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background-color: #2F99A4;
|
||||
color: #FFFFFF;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #257880;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(47, 153, 164, 0.3);
|
||||
}
|
||||
|
||||
.btn-primary:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Secondary Button */
|
||||
.btn-secondary {
|
||||
background-color: #FF4D00;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: #CC3D00;
|
||||
box-shadow: 0 4px 12px rgba(255, 77, 0, 0.3);
|
||||
}
|
||||
|
||||
/* Outline Button */
|
||||
.btn-outline {
|
||||
background-color: transparent;
|
||||
color: #2F99A4;
|
||||
border: 2px solid #2F99A4;
|
||||
}
|
||||
|
||||
.btn-outline:hover {
|
||||
background-color: #2F99A4;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
/* Button Sizes */
|
||||
.btn-small {
|
||||
padding: 8px 24px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.btn-large {
|
||||
padding: 16px 48px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
/* Button States */
|
||||
.btn:disabled,
|
||||
.btn[disabled] {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
```
|
||||
|
||||
**Button Usage:**
|
||||
```html
|
||||
<!-- Primary action -->
|
||||
<button class="btn-primary">Get Started</button>
|
||||
|
||||
<!-- Secondary action -->
|
||||
<button class="btn-secondary">Learn More</button>
|
||||
|
||||
<!-- Tertiary action -->
|
||||
<button class="btn-outline">Contact Us</button>
|
||||
|
||||
<!-- Sizes -->
|
||||
<button class="btn-primary btn-small">Small</button>
|
||||
<button class="btn-primary">Default</button>
|
||||
<button class="btn-primary btn-large">Large</button>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Cards
|
||||
|
||||
```css
|
||||
.card {
|
||||
background: #FFFFFF;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
.card-image {
|
||||
width: 100%;
|
||||
height: 240px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-family: 'Raleway', sans-serif;
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: #585961;
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.card-text {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: #585961;
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
padding: var(--spacing-md) var(--spacing-lg);
|
||||
border-top: 1px solid #E5E5E5;
|
||||
background-color: #F5F5F5;
|
||||
}
|
||||
```
|
||||
|
||||
**Card Usage:**
|
||||
```html
|
||||
<div class="card">
|
||||
<img src="image.jpg" alt="Card image" class="card-image">
|
||||
<div class="card-content">
|
||||
<h3 class="card-title">Card Title</h3>
|
||||
<p class="card-text">Card description goes here.</p>
|
||||
<a href="#" class="btn-primary">Read More</a>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<span class="card-meta">Posted: January 1, 2025</span>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Navigation
|
||||
|
||||
```css
|
||||
/* Main Navigation */
|
||||
.navbar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
padding: var(--spacing-md) 0;
|
||||
}
|
||||
|
||||
.navbar-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 var(--spacing-lg);
|
||||
}
|
||||
|
||||
.navbar-logo {
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.navbar-menu {
|
||||
display: flex;
|
||||
gap: var(--spacing-lg);
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.navbar-link {
|
||||
font-family: 'Raleway', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: #585961;
|
||||
text-decoration: none;
|
||||
padding: 8px 16px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.navbar-link:hover,
|
||||
.navbar-link.active {
|
||||
color: #2F99A4;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Mobile Menu Toggle */
|
||||
.navbar-toggle {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.navbar-toggle span {
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 3px;
|
||||
background: #2F99A4;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.navbar-toggle {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.navbar-menu {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
flex-direction: column;
|
||||
background: #FFFFFF;
|
||||
padding: var(--spacing-lg);
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
|
||||
transform: translateY(-100%);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.navbar-menu.active {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Forms
|
||||
|
||||
```css
|
||||
.form-group {
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #585961;
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-textarea,
|
||||
.form-select {
|
||||
width: 100%;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-size: 16px;
|
||||
color: #585961;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid #CCCDCC;
|
||||
border-radius: 4px;
|
||||
background: #FFFFFF;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.form-input:focus,
|
||||
.form-textarea:focus,
|
||||
.form-select:focus {
|
||||
outline: none;
|
||||
border-color: #2F99A4;
|
||||
box-shadow: 0 0 0 3px rgba(47, 153, 164, 0.1);
|
||||
}
|
||||
|
||||
.form-input::placeholder {
|
||||
color: #8A8B93;
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
min-height: 120px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.form-error {
|
||||
color: #DC3545;
|
||||
font-size: 14px;
|
||||
margin-top: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.form-input.error {
|
||||
border-color: #DC3545;
|
||||
}
|
||||
|
||||
.form-help {
|
||||
font-size: 14px;
|
||||
color: #8A8B93;
|
||||
margin-top: var(--spacing-xs);
|
||||
}
|
||||
```
|
||||
|
||||
**Form Example:**
|
||||
```html
|
||||
<form class="form">
|
||||
<div class="form-group">
|
||||
<label for="name" class="form-label">Name*</label>
|
||||
<input type="text" id="name" class="form-input" placeholder="Your name" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="email" class="form-label">Email*</label>
|
||||
<input type="email" id="email" class="form-input" placeholder="your@email.com" required>
|
||||
<small class="form-help">We'll never share your email.</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="message" class="form-label">Message*</label>
|
||||
<textarea id="message" class="form-textarea" placeholder="Your message" required></textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn-primary">Send Message</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Links
|
||||
|
||||
```css
|
||||
/* Body Links */
|
||||
a {
|
||||
color: #2F99A4;
|
||||
text-decoration: none;
|
||||
transition: all 0.3s ease;
|
||||
border-bottom: 1px solid transparent;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #257880;
|
||||
border-bottom-color: #2F99A4;
|
||||
}
|
||||
|
||||
/* External Links */
|
||||
a[target="_blank"]::after {
|
||||
content: ' ↗';
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* Download Links */
|
||||
a[download]::before {
|
||||
content: '⬇ ';
|
||||
}
|
||||
|
||||
/* Standalone Links */
|
||||
.link-standalone {
|
||||
font-family: 'Raleway', sans-serif;
|
||||
font-weight: 600;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.link-standalone::after {
|
||||
content: '→';
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.link-standalone:hover::after {
|
||||
transform: translateX(4px);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Hero Sections
|
||||
|
||||
```css
|
||||
.hero {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 600px;
|
||||
padding: var(--spacing-4xl) var(--spacing-lg);
|
||||
background: linear-gradient(135deg, #2F99A4 0%, #257880 100%);
|
||||
color: #FFFFFF;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
max-width: 800px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-family: 'Raleway', sans-serif;
|
||||
font-size: 56px;
|
||||
font-weight: 700;
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-size: 20px;
|
||||
line-height: 1.6;
|
||||
margin-bottom: var(--spacing-xl);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.hero-cta {
|
||||
display: flex;
|
||||
gap: var(--spacing-md);
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.hero {
|
||||
min-height: 400px;
|
||||
padding: var(--spacing-xxl) var(--spacing-lg);
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 36px;
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.hero-cta {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Tables
|
||||
|
||||
```css
|
||||
.table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: var(--spacing-lg);
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.table thead {
|
||||
background-color: #2F99A4;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.table th {
|
||||
font-family: 'Raleway', sans-serif;
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.table td {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #E5E5E5;
|
||||
}
|
||||
|
||||
.table tbody tr:hover {
|
||||
background-color: #F5F5F5;
|
||||
}
|
||||
|
||||
.table tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* Responsive Table */
|
||||
@media (max-width: 768px) {
|
||||
.table {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Footer
|
||||
|
||||
```css
|
||||
.footer {
|
||||
background-color: #585961;
|
||||
color: #FFFFFF;
|
||||
padding: var(--spacing-4xl) 0 var(--spacing-lg);
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: var(--spacing-xl);
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 var(--spacing-lg);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.footer-section h4 {
|
||||
font-family: 'Raleway', sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #FFFFFF;
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.footer-section ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.footer-section li {
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.footer-section a {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
text-decoration: none;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.footer-section a:hover {
|
||||
color: #2F99A4;
|
||||
}
|
||||
|
||||
.footer-bottom {
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
padding-top: var(--spacing-lg);
|
||||
text-align: center;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 14px;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Layout Patterns
|
||||
|
||||
### Two-Column Layout
|
||||
|
||||
```css
|
||||
.two-column {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
gap: var(--spacing-xl);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.two-column {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Three-Column Layout
|
||||
|
||||
```css
|
||||
.three-column {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: var(--spacing-lg);
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.three-column {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.three-column {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Content + Sidebar
|
||||
|
||||
```css
|
||||
.content-sidebar {
|
||||
display: grid;
|
||||
grid-template-columns: 3fr 1fr;
|
||||
gap: var(--spacing-xxl);
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.content-sidebar {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Animations & Transitions
|
||||
|
||||
```css
|
||||
/* Smooth transitions */
|
||||
* {
|
||||
transition-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
/* Fade In */
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.fade-in {
|
||||
animation: fadeIn 0.6s ease-out;
|
||||
}
|
||||
|
||||
/* Slide In */
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Pulse (for emphasis) */
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Web Design Checklist
|
||||
|
||||
Before launching any web project:
|
||||
|
||||
**Layout:**
|
||||
- [ ] High white space maintained throughout
|
||||
- [ ] Responsive grid implemented correctly
|
||||
- [ ] All breakpoints tested (mobile, tablet, desktop)
|
||||
- [ ] Content max-width appropriate (≤1200px)
|
||||
|
||||
**Components:**
|
||||
- [ ] Buttons follow brand styles (Raleway font, correct colors)
|
||||
- [ ] Cards have appropriate shadows and hover states
|
||||
- [ ] Navigation sticky/fixed and accessible
|
||||
- [ ] Forms styled consistently with validation
|
||||
|
||||
**Typography:**
|
||||
- [ ] Headlines use Raleway
|
||||
- [ ] Body text uses Open Sans
|
||||
- [ ] Font sizes scale responsively
|
||||
- [ ] Line-heights appropriate for readability
|
||||
|
||||
**Colors:**
|
||||
- [ ] Turquoise primary, orange accent only
|
||||
- [ ] WCAG AA contrast compliance verified
|
||||
- [ ] Brand colors match exactly (no variations)
|
||||
|
||||
**Performance:**
|
||||
- [ ] Images optimized and responsive
|
||||
- [ ] Fonts loaded efficiently (preload critical)
|
||||
- [ ] CSS minified for production
|
||||
- [ ] Animations use transform/opacity (GPU-accelerated)
|
||||
|
||||
**Accessibility:**
|
||||
- [ ] All interactive elements keyboard accessible
|
||||
- [ ] ARIA labels where needed
|
||||
- [ ] Focus states visible
|
||||
- [ ] Screen reader tested
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2025-10-18*
|
||||
*Maintained by: Netresearch DTT GmbH*
|
||||
285
skills/netresearch-branding/templates/landing-page.html
Normal file
285
skills/netresearch-branding/templates/landing-page.html
Normal file
@@ -0,0 +1,285 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Netresearch - Landing Page Template</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Raleway:wght@400;600;700&family=Open+Sans:wght@400;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar">
|
||||
<div class="navbar-container">
|
||||
<img src="logo.svg" alt="Netresearch Logo" class="navbar-logo">
|
||||
|
||||
<button class="navbar-toggle" aria-label="Toggle navigation">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</button>
|
||||
|
||||
<ul class="navbar-menu">
|
||||
<li><a href="#services" class="navbar-link active">Services</a></li>
|
||||
<li><a href="#about" class="navbar-link">About</a></li>
|
||||
<li><a href="#portfolio" class="navbar-link">Portfolio</a></li>
|
||||
<li><a href="#contact" class="navbar-link">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Hero Section -->
|
||||
<section class="hero">
|
||||
<div class="hero-content">
|
||||
<h1 class="hero-title">Your Digital Transformation Partner</h1>
|
||||
<p class="hero-subtitle">
|
||||
We create innovative digital solutions that drive business growth and enhance user experiences.
|
||||
</p>
|
||||
<div class="hero-cta">
|
||||
<a href="#contact" class="btn-primary btn-large">Get Started</a>
|
||||
<a href="#services" class="btn-outline btn-large">Learn More</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Services Section -->
|
||||
<section id="services" class="section">
|
||||
<div class="container">
|
||||
<h2 class="section-title">Our Services</h2>
|
||||
<p class="section-subtitle">
|
||||
Comprehensive digital solutions tailored to your needs
|
||||
</p>
|
||||
|
||||
<div class="three-column">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<h3 class="card-title">Web Development</h3>
|
||||
<p class="card-text">
|
||||
Modern, responsive websites built with the latest technologies and best practices.
|
||||
</p>
|
||||
<a href="#" class="link-standalone">Learn more</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<h3 class="card-title">E-Commerce Solutions</h3>
|
||||
<p class="card-text">
|
||||
Powerful online stores that drive sales and provide exceptional shopping experiences.
|
||||
</p>
|
||||
<a href="#" class="link-standalone">Learn more</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<h3 class="card-title">Digital Strategy</h3>
|
||||
<p class="card-text">
|
||||
Strategic consulting to help your business thrive in the digital landscape.
|
||||
</p>
|
||||
<a href="#" class="link-standalone">Learn more</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- About Section -->
|
||||
<section id="about" class="section section-alt">
|
||||
<div class="container">
|
||||
<div class="two-column">
|
||||
<div>
|
||||
<h2>About Netresearch</h2>
|
||||
<p class="lead">
|
||||
With over 20 years of experience, we've been helping businesses succeed online through innovative digital solutions.
|
||||
</p>
|
||||
<p>
|
||||
Our team of experts combines technical excellence with creative thinking to deliver projects that exceed expectations. We specialize in TYPO3, e-commerce, and custom web applications.
|
||||
</p>
|
||||
<p>
|
||||
Based in Leipzig, Germany, we serve clients across Europe and beyond, providing end-to-end digital services from strategy to implementation and support.
|
||||
</p>
|
||||
<a href="#contact" class="btn-primary">Work with us</a>
|
||||
</div>
|
||||
<div>
|
||||
<img src="about-image.jpg" alt="Netresearch team" style="width: 100%; border-radius: 8px;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Portfolio Section -->
|
||||
<section id="portfolio" class="section">
|
||||
<div class="container">
|
||||
<h2 class="section-title">Our Work</h2>
|
||||
<p class="section-subtitle">
|
||||
Recent projects we're proud of
|
||||
</p>
|
||||
|
||||
<div class="three-column">
|
||||
<div class="card">
|
||||
<img src="project-1.jpg" alt="Project 1" class="card-image">
|
||||
<div class="card-content">
|
||||
<h3 class="card-title">E-Commerce Platform</h3>
|
||||
<p class="card-text">
|
||||
Complete redesign and development of a multi-vendor marketplace.
|
||||
</p>
|
||||
<div class="card-footer">
|
||||
<span class="card-meta">TYPO3 • Magento • 2024</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<img src="project-2.jpg" alt="Project 2" class="card-image">
|
||||
<div class="card-content">
|
||||
<h3 class="card-title">Corporate Website</h3>
|
||||
<p class="card-text">
|
||||
Modern website with multilingual content and advanced integrations.
|
||||
</p>
|
||||
<div class="card-footer">
|
||||
<span class="card-meta">TYPO3 • API • 2024</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<img src="project-3.jpg" alt="Project 3" class="card-image">
|
||||
<div class="card-content">
|
||||
<h3 class="card-title">Custom Web Application</h3>
|
||||
<p class="card-text">
|
||||
Bespoke solution for complex business requirements and workflows.
|
||||
</p>
|
||||
<div class="card-footer">
|
||||
<span class="card-meta">Laravel • Vue.js • 2024</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Contact Section -->
|
||||
<section id="contact" class="section section-alt">
|
||||
<div class="container">
|
||||
<div class="two-column">
|
||||
<div>
|
||||
<h2>Get in Touch</h2>
|
||||
<p class="lead">
|
||||
Ready to start your next project? Let's talk about how we can help.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Netresearch DTT GmbH</strong><br>
|
||||
Nonnenstraße 11d<br>
|
||||
04229 Leipzig, Germany
|
||||
</p>
|
||||
<p>
|
||||
<strong>Phone:</strong> +49 341 49288-0<br>
|
||||
<strong>Email:</strong> <a href="mailto:info@netresearch.de">info@netresearch.de</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<form class="form">
|
||||
<div class="form-group">
|
||||
<label for="name" class="form-label">Name*</label>
|
||||
<input type="text" id="name" class="form-input" placeholder="Your name" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="email" class="form-label">Email*</label>
|
||||
<input type="email" id="email" class="form-input" placeholder="your@email.com" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="subject" class="form-label">Subject*</label>
|
||||
<input type="text" id="subject" class="form-input" placeholder="Project inquiry" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="message" class="form-label">Message*</label>
|
||||
<textarea id="message" class="form-textarea" placeholder="Tell us about your project" required></textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn-primary btn-large">Send Message</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="footer-content">
|
||||
<div class="footer-section">
|
||||
<h4>Company</h4>
|
||||
<ul>
|
||||
<li><a href="#about">About Us</a></li>
|
||||
<li><a href="#services">Services</a></li>
|
||||
<li><a href="#portfolio">Portfolio</a></li>
|
||||
<li><a href="#careers">Careers</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer-section">
|
||||
<h4>Services</h4>
|
||||
<ul>
|
||||
<li><a href="#web-dev">Web Development</a></li>
|
||||
<li><a href="#ecommerce">E-Commerce</a></li>
|
||||
<li><a href="#typo3">TYPO3</a></li>
|
||||
<li><a href="#consulting">Consulting</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer-section">
|
||||
<h4>Resources</h4>
|
||||
<ul>
|
||||
<li><a href="#blog">Blog</a></li>
|
||||
<li><a href="#case-studies">Case Studies</a></li>
|
||||
<li><a href="#documentation">Documentation</a></li>
|
||||
<li><a href="#support">Support</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer-section">
|
||||
<h4>Legal</h4>
|
||||
<ul>
|
||||
<li><a href="#imprint">Imprint</a></li>
|
||||
<li><a href="#privacy">Privacy Policy</a></li>
|
||||
<li><a href="#terms">Terms of Service</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-bottom">
|
||||
<div class="container">
|
||||
<p>© 2025 Netresearch DTT GmbH. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
// Mobile menu toggle
|
||||
const toggle = document.querySelector('.navbar-toggle');
|
||||
const menu = document.querySelector('.navbar-menu');
|
||||
|
||||
toggle.addEventListener('click', () => {
|
||||
menu.classList.toggle('active');
|
||||
});
|
||||
|
||||
// Smooth scrolling
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(this.getAttribute('href'));
|
||||
if (target) {
|
||||
target.scrollIntoView({ behavior: 'smooth' });
|
||||
menu.classList.remove('active');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
612
skills/netresearch-branding/templates/styles.css
Normal file
612
skills/netresearch-branding/templates/styles.css
Normal file
@@ -0,0 +1,612 @@
|
||||
/*
|
||||
* Netresearch Brand Styles
|
||||
* Complete CSS template following brand guidelines
|
||||
*/
|
||||
|
||||
/* ============================================
|
||||
CSS CUSTOM PROPERTIES
|
||||
============================================ */
|
||||
:root {
|
||||
/* Brand Colors */
|
||||
--color-primary: #2F99A4;
|
||||
--color-primary-rgb: 47, 153, 164;
|
||||
--color-primary-dark: #257880;
|
||||
--color-accent: #FF4D00;
|
||||
--color-accent-rgb: 255, 77, 0;
|
||||
--color-accent-dark: #CC3D00;
|
||||
|
||||
/* Neutral Colors */
|
||||
--color-text-primary: #585961;
|
||||
--color-text-secondary: #8A8B93;
|
||||
--color-background: #FFFFFF;
|
||||
--color-background-alt: #F5F5F5;
|
||||
--color-border: #CCCDCC;
|
||||
--color-border-light: #E5E5E5;
|
||||
|
||||
/* Typography */
|
||||
--font-headline: 'Raleway', sans-serif;
|
||||
--font-body: 'Open Sans', sans-serif;
|
||||
|
||||
/* Font Sizes */
|
||||
--font-size-xs: 12px;
|
||||
--font-size-sm: 14px;
|
||||
--font-size-base: 16px;
|
||||
--font-size-lg: 18px;
|
||||
--font-size-xl: 20px;
|
||||
--font-size-2xl: 24px;
|
||||
--font-size-3xl: 28px;
|
||||
--font-size-4xl: 36px;
|
||||
--font-size-5xl: 48px;
|
||||
--font-size-6xl: 56px;
|
||||
|
||||
/* Spacing */
|
||||
--spacing-xs: 8px;
|
||||
--spacing-sm: 12px;
|
||||
--spacing-md: 16px;
|
||||
--spacing-lg: 24px;
|
||||
--spacing-xl: 32px;
|
||||
--spacing-xxl: 48px;
|
||||
--spacing-3xl: 64px;
|
||||
--spacing-4xl: 96px;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.12);
|
||||
--shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.16);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
RESET & BASE STYLES
|
||||
============================================ */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--font-size-base);
|
||||
line-height: 1.6;
|
||||
color: var(--color-text-primary);
|
||||
background-color: var(--color-background);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
TYPOGRAPHY
|
||||
============================================ */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: var(--font-headline);
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: var(--font-size-5xl);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: var(--font-size-4xl);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: var(--font-size-3xl);
|
||||
font-weight: 600;
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: 600;
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.lead {
|
||||
font-size: var(--font-size-xl);
|
||||
font-weight: 400;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--color-primary);
|
||||
text-decoration: none;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--color-primary-dark);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
LAYOUT
|
||||
============================================ */
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 var(--spacing-lg);
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: var(--spacing-4xl) 0;
|
||||
}
|
||||
|
||||
.section-alt {
|
||||
background-color: var(--color-background-alt);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
text-align: center;
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.section-subtitle {
|
||||
text-align: center;
|
||||
font-size: var(--font-size-xl);
|
||||
color: var(--color-text-secondary);
|
||||
margin-bottom: var(--spacing-xxl);
|
||||
}
|
||||
|
||||
.two-column {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--spacing-xl);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.three-column {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: var(--spacing-lg);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
NAVIGATION
|
||||
============================================ */
|
||||
.navbar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
background: var(--color-background);
|
||||
box-shadow: var(--shadow-sm);
|
||||
padding: var(--spacing-md) 0;
|
||||
}
|
||||
|
||||
.navbar-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 var(--spacing-lg);
|
||||
}
|
||||
|
||||
.navbar-logo {
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.navbar-menu {
|
||||
display: flex;
|
||||
gap: var(--spacing-lg);
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.navbar-link {
|
||||
font-family: var(--font-headline);
|
||||
font-size: var(--font-size-base);
|
||||
font-weight: 400;
|
||||
color: var(--color-text-primary);
|
||||
padding: var(--spacing-xs) var(--spacing-md);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.navbar-link:hover,
|
||||
.navbar-link.active {
|
||||
color: var(--color-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.navbar-toggle {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.navbar-toggle span {
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 3px;
|
||||
background: var(--color-primary);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
HERO SECTION
|
||||
============================================ */
|
||||
.hero {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 600px;
|
||||
padding: var(--spacing-4xl) var(--spacing-lg);
|
||||
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-dark) 100%);
|
||||
color: var(--color-background);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
max-width: 800px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
color: var(--color-background);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-size: var(--font-size-xl);
|
||||
line-height: 1.6;
|
||||
margin-bottom: var(--spacing-xl);
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.hero-cta {
|
||||
display: flex;
|
||||
gap: var(--spacing-md);
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
BUTTONS
|
||||
============================================ */
|
||||
.btn-primary,
|
||||
.btn-secondary,
|
||||
.btn-outline {
|
||||
display: inline-block;
|
||||
font-family: var(--font-headline);
|
||||
font-size: var(--font-size-base);
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
padding: 12px 32px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-background);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: var(--color-primary-dark);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(47, 153, 164, 0.3);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: var(--color-accent);
|
||||
color: var(--color-background);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: var(--color-accent-dark);
|
||||
box-shadow: 0 4px 12px rgba(255, 77, 0, 0.3);
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background-color: transparent;
|
||||
color: var(--color-background);
|
||||
border: 2px solid var(--color-background);
|
||||
}
|
||||
|
||||
.btn-outline:hover {
|
||||
background-color: var(--color-background);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.btn-large {
|
||||
padding: 16px 48px;
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
.btn-small {
|
||||
padding: 8px 24px;
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
CARDS
|
||||
============================================ */
|
||||
.card {
|
||||
background: var(--color-background);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
.card-image {
|
||||
width: 100%;
|
||||
height: 240px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: 600;
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.card-text {
|
||||
font-size: var(--font-size-base);
|
||||
line-height: 1.6;
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
padding: var(--spacing-md) var(--spacing-lg);
|
||||
border-top: 1px solid var(--color-border-light);
|
||||
background-color: var(--color-background-alt);
|
||||
}
|
||||
|
||||
.card-meta {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
LINKS
|
||||
============================================ */
|
||||
.link-standalone {
|
||||
font-family: var(--font-headline);
|
||||
font-weight: 600;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.link-standalone::after {
|
||||
content: '→';
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.link-standalone:hover::after {
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
FORMS
|
||||
============================================ */
|
||||
.form-group {
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 600;
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-textarea {
|
||||
width: 100%;
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--font-size-base);
|
||||
color: var(--color-text-primary);
|
||||
padding: 12px 16px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 4px;
|
||||
background: var(--color-background);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.form-input:focus,
|
||||
.form-textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 3px rgba(47, 153, 164, 0.1);
|
||||
}
|
||||
|
||||
.form-input::placeholder,
|
||||
.form-textarea::placeholder {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
min-height: 120px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
FOOTER
|
||||
============================================ */
|
||||
.footer {
|
||||
background-color: var(--color-text-primary);
|
||||
color: var(--color-background);
|
||||
padding: var(--spacing-4xl) 0 var(--spacing-lg);
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: var(--spacing-xl);
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 var(--spacing-lg);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.footer-section h4 {
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: 600;
|
||||
color: var(--color-background);
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.footer-section ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.footer-section li {
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.footer-section a {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
text-decoration: none;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.footer-section a:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.footer-bottom {
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
padding-top: var(--spacing-lg);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer-bottom p {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: var(--font-size-sm);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
RESPONSIVE DESIGN
|
||||
============================================ */
|
||||
|
||||
/* Tablet (768px and below) */
|
||||
@media (max-width: 1024px) {
|
||||
.three-column {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
/* Typography */
|
||||
h1 { font-size: var(--font-size-4xl); }
|
||||
h2 { font-size: var(--font-size-3xl); }
|
||||
h3 { font-size: var(--font-size-2xl); }
|
||||
|
||||
/* Layout */
|
||||
.two-column,
|
||||
.three-column {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
/* Hero */
|
||||
.hero {
|
||||
min-height: 400px;
|
||||
padding: var(--spacing-xxl) var(--spacing-lg);
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: var(--font-size-4xl);
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
.hero-cta {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Navigation */
|
||||
.navbar-toggle {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.navbar-menu {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
flex-direction: column;
|
||||
background: var(--color-background);
|
||||
padding: var(--spacing-lg);
|
||||
box-shadow: var(--shadow-md);
|
||||
transform: translateY(-100%);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.navbar-menu.active {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* Sections */
|
||||
.section {
|
||||
padding: var(--spacing-xxl) 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile (600px and below) */
|
||||
@media (max-width: 600px) {
|
||||
h1 { font-size: var(--font-size-3xl); }
|
||||
h2 { font-size: var(--font-size-2xl); }
|
||||
|
||||
.hero {
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.btn-large {
|
||||
padding: 12px 32px;
|
||||
font-size: var(--font-size-base);
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
UTILITIES
|
||||
============================================ */
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mb-0 { margin-bottom: 0; }
|
||||
.mb-sm { margin-bottom: var(--spacing-sm); }
|
||||
.mb-md { margin-bottom: var(--spacing-md); }
|
||||
.mb-lg { margin-bottom: var(--spacing-lg); }
|
||||
.mb-xl { margin-bottom: var(--spacing-xl); }
|
||||
11
skills/typo3-conformance/.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
11
skills/typo3-conformance/.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: TYPO3 Slack
|
||||
url: https://typo3.org/community/meet/chat-slack
|
||||
about: Join the TYPO3 community on Slack for discussions and support
|
||||
- name: TYPO3 Extension Documentation
|
||||
url: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/Index.html
|
||||
about: Official TYPO3 extension architecture and development documentation
|
||||
- name: TYPO3 Coding Guidelines
|
||||
url: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/CodingGuidelines/Index.html
|
||||
about: TYPO3 coding guidelines (CGL) and PSR-12 standards
|
||||
51
skills/typo3-conformance/.github/workflows/publish-to-ter.yml
vendored
Normal file
51
skills/typo3-conformance/.github/workflows/publish-to-ter.yml
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
name: Publish new extension version to TER
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish new version to TER
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TYPO3_EXTENSION_KEY: ${{ secrets.TYPO3_EXTENSION_KEY }}
|
||||
TYPO3_API_TOKEN: ${{ secrets.TYPO3_TER_ACCESS_TOKEN }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Check tag
|
||||
run: |
|
||||
if ! [[ ${{ github.ref }} =~ ^refs/tags/v[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$ ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Get version
|
||||
id: get-version
|
||||
run: echo "version=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Get comment
|
||||
id: get-comment
|
||||
run: |
|
||||
readonly local comment=$(git tag -n10 -l v${{ env.version }} | sed "s/^v[0-9.]*[ ]*//g")
|
||||
|
||||
if [[ -z "${comment// }" ]]; then
|
||||
echo "comment=Released version ${{ env.version }} of ${{ env.TYPO3_EXTENSION_KEY }} -- for details see $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases" >> $GITHUB_ENV
|
||||
else
|
||||
echo "comment=$comment -- for details see $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 8.3
|
||||
extensions: intl, mbstring, json, zip, curl
|
||||
tools: composer:v2
|
||||
|
||||
- name: Install tailor
|
||||
run: composer global require typo3/tailor --prefer-dist --no-progress --no-suggest
|
||||
|
||||
- name: Publish to TER
|
||||
run: php ~/.composer/vendor/bin/tailor ter:publish --comment "${{ env.comment }}" ${{ env.version }}
|
||||
2
skills/typo3-conformance/.gitignore
vendored
Normal file
2
skills/typo3-conformance/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.serena/
|
||||
claudedocs/
|
||||
1281
skills/typo3-conformance/README.md
Normal file
1281
skills/typo3-conformance/README.md
Normal file
File diff suppressed because it is too large
Load Diff
991
skills/typo3-conformance/SKILL.md
Normal file
991
skills/typo3-conformance/SKILL.md
Normal file
@@ -0,0 +1,991 @@
|
||||
---
|
||||
name: typo3-conformance
|
||||
description: "Evaluate TYPO3 extensions for conformance to TYPO3 12/13 LTS standards, coding guidelines (PSR-12), and architecture patterns. Use when assessing extension quality, generating conformance reports, identifying technical debt, or planning modernization. Validates: extension architecture, composer.json, ext_emconf.php, ext_* files, v13 deprecations, backend module v13 compliance (ES6 modules, DocHeader, Modal/Notification APIs, Module.html layout, ARIA, extension key consistency, CSRF, icons), dependency injection, services, testing, Extbase patterns, Crowdin, GitHub workflows. Dual scoring (0-100 base + 0-22 excellence). Delegates to typo3-tests and typo3-docs skills for deep analysis. PHP 8.1-8.4 support."
|
||||
license: Complete terms in LICENSE.txt
|
||||
---
|
||||
|
||||
# TYPO3 Extension Conformance Checker
|
||||
|
||||
**Purpose:** Evaluate TYPO3 extensions for conformance to official TYPO3 coding standards, architecture patterns, and best practices.
|
||||
|
||||
**Activation:** This skill activates when analyzing TYPO3 extensions for standards compliance, code quality, or conformance checking.
|
||||
|
||||
## Skill Ecosystem Integration
|
||||
|
||||
Delegate to specialized skills for deep domain analysis:
|
||||
|
||||
**🔧 typo3-tests** (https://github.com/netresearch/typo3-testing-skill)
|
||||
- Deep PHPUnit configuration analysis
|
||||
- Test quality patterns (AAA, mocking, fixtures)
|
||||
- TYPO3 Testing Framework validation
|
||||
- Accurate coverage calculation
|
||||
- Test anti-pattern detection
|
||||
|
||||
**📚 typo3-docs** (https://github.com/netresearch/typo3-docs-skill)
|
||||
- RST syntax and TYPO3 directive validation
|
||||
- Documentation rendering with Docker
|
||||
- Modern tooling detection (guides.xml, screenshots.json)
|
||||
- Cross-reference integrity checks
|
||||
- Official TYPO3 documentation standards
|
||||
|
||||
**Delegation Strategy:**
|
||||
- **Surface-level checks:** Perform directly with this skill
|
||||
- **Deep analysis:** Delegate to specialized skills when available
|
||||
- **Fallback:** Apply basic validation if specialized skills unavailable
|
||||
- **Integration:** Incorporate results into conformance scoring
|
||||
|
||||
## Version Compatibility
|
||||
|
||||
**Target Standards:**
|
||||
- **TYPO3:** 12.4 LTS / 13.x
|
||||
- **PHP:** 8.1 / 8.2 / 8.3 / 8.4
|
||||
- **TYPO3 12 LTS:** Supports PHP 8.1 - 8.4
|
||||
- **TYPO3 13 LTS:** Requires PHP 8.2 - 8.4
|
||||
|
||||
**Reference:** See `references/version-requirements.md` for complete version compatibility matrix and migration paths.
|
||||
|
||||
## Critical Validation References
|
||||
|
||||
**New Advanced Validation Guides:**
|
||||
- **`references/runtests-validation.md`** - Validate Build/Scripts/runTests.sh against Tea extension reference
|
||||
- **`references/development-environment.md`** - Validate DDEV/Docker development environment setup
|
||||
- **`references/directory-structure.md`** - Validate .Build/ vs Build/ directory separation
|
||||
|
||||
These guides provide line-by-line validation strategies, automated validation scripts, and scoring methodologies to ensure comprehensive conformance checks.
|
||||
|
||||
## Evaluation Workflow
|
||||
|
||||
### Step 1: Initial Assessment
|
||||
|
||||
**Identify Extension Context:**
|
||||
- Extension key and location
|
||||
- TYPO3 version compatibility (check composer.json)
|
||||
- Extension type (Extbase plugin, backend module, content element, etc.)
|
||||
- Scope of evaluation (full extension vs specific components)
|
||||
|
||||
**Quick Directory Scan:**
|
||||
```bash
|
||||
# Check for presence of key directories
|
||||
ls -la | grep -E "Classes|Configuration|Resources|Tests|Documentation"
|
||||
|
||||
# Verify required files
|
||||
ls -1 | grep -E "composer.json|ext_emconf.php"
|
||||
|
||||
# Check documentation
|
||||
ls -1 Documentation/ | grep -E "Index.rst|Settings.cfg"
|
||||
```
|
||||
|
||||
### Step 2: File Structure Analysis
|
||||
|
||||
**Reference:** `references/extension-architecture.md`
|
||||
|
||||
**Check Required Files:**
|
||||
- [ ] `composer.json` - Complete with PSR-4 autoloading
|
||||
- [ ] `ext_emconf.php` - Proper metadata structure
|
||||
- [ ] `Documentation/Index.rst` - Main documentation entry
|
||||
- [ ] `Documentation/Settings.cfg` - Documentation settings
|
||||
- [ ] `Classes/` directory exists
|
||||
- [ ] `Configuration/` directory exists
|
||||
- [ ] `Resources/` directory exists
|
||||
|
||||
**Validate Directory Structure:**
|
||||
|
||||
```bash
|
||||
# Check Classes/ organization
|
||||
find Classes/ -type d | head -20
|
||||
|
||||
# Verify Configuration/ structure
|
||||
ls -R Configuration/
|
||||
|
||||
# Check Resources/ separation
|
||||
ls -R Resources/Private/ Resources/Public/
|
||||
```
|
||||
|
||||
**Common Issues to Flag:**
|
||||
- ❌ PHP files in extension root (except ext_* files)
|
||||
- ❌ Mixed directory naming (Controllers/ vs Controller/)
|
||||
- ❌ Tests not mirroring Classes/ structure
|
||||
- ❌ Missing required documentation files
|
||||
- ❌ Non-standard directory names
|
||||
|
||||
**Output Format:**
|
||||
```markdown
|
||||
## File Structure Conformance
|
||||
|
||||
### ✅ Passed
|
||||
- composer.json present with PSR-4 autoloading
|
||||
- Classes/ directory properly organized
|
||||
- Documentation/ complete with Index.rst and Settings.cfg
|
||||
|
||||
### ❌ Failed
|
||||
- Missing Tests/ directory
|
||||
- Configuration/Backend/ not found (backend modules using deprecated ext_tables.php)
|
||||
- Resources/Private/Language/ missing XLIFF files
|
||||
|
||||
### ⚠️ Warnings
|
||||
- Unusual directory: Classes/Helpers/ (should use Utility/)
|
||||
- ext_tables.php present (consider migrating to Configuration/Backend/)
|
||||
```
|
||||
|
||||
### Step 3: Coding Standards Analysis
|
||||
|
||||
**Reference:** `references/coding-guidelines.md`
|
||||
|
||||
**PHP Code Style Checks:**
|
||||
|
||||
```bash
|
||||
# Find all PHP files
|
||||
find Classes/ Tests/ Configuration/ -name "*.php"
|
||||
|
||||
# Check for PSR-12 violations (sample)
|
||||
grep -r "array(" Classes/ # Should use []
|
||||
grep -r "<?php$" Classes/ | wc -l # Missing declare(strict_types=1)
|
||||
```
|
||||
|
||||
**Manual Inspection Points:**
|
||||
|
||||
**Naming Conventions:**
|
||||
- Variables/methods: camelCase
|
||||
- Classes: UpperCamelCase
|
||||
- Constants: SCREAMING_SNAKE_CASE
|
||||
- Namespaces: match directory structure
|
||||
|
||||
**Code Sample Analysis:**
|
||||
```php
|
||||
// Read sample files
|
||||
cat Classes/Controller/ProductController.php
|
||||
cat Classes/Domain/Model/Product.php
|
||||
cat Classes/Domain/Repository/ProductRepository.php
|
||||
```
|
||||
|
||||
**Check for:**
|
||||
- [ ] `declare(strict_types=1)` at top of all PHP files
|
||||
- [ ] Proper PHPDoc comments on classes and public methods
|
||||
- [ ] Type declarations on all properties and parameters
|
||||
- [ ] Short array syntax `[]` (not `array()`)
|
||||
- [ ] Proper namespace structure
|
||||
- [ ] Correct use statements (sorted and grouped)
|
||||
- [ ] Descriptive method names with verbs
|
||||
- [ ] Proper indentation (4 spaces, no tabs)
|
||||
- [ ] **Inclusive language** - no problematic terminology
|
||||
|
||||
**Inclusive Language Check:**
|
||||
|
||||
```bash
|
||||
# Check for non-inclusive terminology
|
||||
grep -ri "master\|slave\|blacklist\|whitelist\|sanity" Classes/ Documentation/ --include="*.php" --include="*.rst" --include="*.md"
|
||||
```
|
||||
|
||||
**Problematic Terms to Avoid:**
|
||||
- ❌ "master/slave" → ✅ "primary/replica", "leader/follower", "main/secondary"
|
||||
- ❌ "blacklist/whitelist" → ✅ "blocklist/allowlist", "denylist/permitlist", "exclusion list/inclusion list"
|
||||
- ❌ "sanity check" → ✅ "confidence check", "validation check", "coherence check"
|
||||
- ❌ "dummy" → ✅ "placeholder", "sample", "test"
|
||||
- ❌ "grandfathered" → ✅ "legacy status", "existing entitlement"
|
||||
|
||||
**TYPO3 Community Values:**
|
||||
The TYPO3 community is committed to inclusive language that welcomes all contributors. Code, comments, and documentation should use terminology that is respectful and professional.
|
||||
|
||||
**Output Format:**
|
||||
```markdown
|
||||
## Coding Standards Conformance
|
||||
|
||||
### ✅ Strengths
|
||||
- All classes use UpperCamelCase naming
|
||||
- Proper type declarations on methods
|
||||
- PHPDoc comments present and complete
|
||||
|
||||
### ❌ Violations
|
||||
- 15 files missing `declare(strict_types=1)`
|
||||
- Classes/Controller/ProductController.php:1
|
||||
- Classes/Service/CalculationService.php:1
|
||||
- 8 instances of old array syntax `array()`
|
||||
- Classes/Utility/ArrayHelper.php:45
|
||||
- Classes/Domain/Model/Product.php:78
|
||||
- 3 methods missing PHPDoc comments
|
||||
- Classes/Service/EmailService.php:calculate()
|
||||
- **5 instances of non-inclusive terminology**
|
||||
- Classes/Service/FilterService.php:12 - "whitelist" → use "inclusion list" or "allowlist"
|
||||
- Classes/Service/FilterService.php:45 - "blacklist" → use "exclusion list" or "blocklist"
|
||||
- Documentation/Configuration/Index.rst:78 - "master configuration" → use "primary configuration" or "main configuration"
|
||||
|
||||
### ⚠️ Style Issues
|
||||
- Inconsistent spacing around concatenation operators (12 instances)
|
||||
- Some variables using snake_case (5 instances)
|
||||
```
|
||||
|
||||
### Step 3.5: Backend Module v13 Compliance (If Applicable)
|
||||
|
||||
**Reference:** `references/backend-module-v13.md`
|
||||
|
||||
**Trigger:** Extension contains backend modules (Configuration/Backend/Modules.php or ext_tables.php with registerModule)
|
||||
|
||||
**Critical Checks:**
|
||||
|
||||
**Extension Key Consistency:**
|
||||
```bash
|
||||
# Check for mixed extension keys
|
||||
grep -rn "EXT:.*/" Resources/Private/Templates/ | grep -v "EXT:${EXTENSION_KEY}/"
|
||||
|
||||
# Verify JavaScript uses correct name
|
||||
grep -rn "alert\|console" Resources/Public/JavaScript/
|
||||
```
|
||||
|
||||
**JavaScript Modernization:**
|
||||
```bash
|
||||
# Check for inline JavaScript (VIOLATION)
|
||||
grep -rn "FooterAssets" Resources/Private/Templates/
|
||||
grep -rn "<script" Resources/Private/Templates/
|
||||
|
||||
# Verify ES6 module exists
|
||||
ls Resources/Public/JavaScript/BackendModule.js
|
||||
|
||||
# Check Modal/Notification API usage
|
||||
grep -E "Modal\.confirm|Notification\.(success|error)" Resources/Public/JavaScript/*.js
|
||||
```
|
||||
|
||||
**Layout Pattern:**
|
||||
```bash
|
||||
# Verify Module.html layout
|
||||
ls Resources/Private/Layouts/Module.html
|
||||
|
||||
# Check all templates use Module layout
|
||||
grep -n "f:layout name=" Resources/Private/Templates/Backend/**/*.html
|
||||
```
|
||||
|
||||
**DocHeader Integration:**
|
||||
```bash
|
||||
# Check IconFactory injection
|
||||
grep "IconFactory" Classes/Controller/Backend/*.php
|
||||
|
||||
# Verify DocHeader buttons
|
||||
grep "addDocHeaderButtons\|makeLinkButton\|makeShortcutButton" Classes/Controller/Backend/*.php
|
||||
```
|
||||
|
||||
**CSRF Protection:**
|
||||
```bash
|
||||
# Check for hardcoded URLs (VIOLATION)
|
||||
grep -rn '"/typo3/' Resources/
|
||||
|
||||
# Verify uriBuilder usage
|
||||
grep "uriFor(" Classes/Controller/Backend/*.php
|
||||
```
|
||||
|
||||
**Accessibility:**
|
||||
```bash
|
||||
# Check ARIA labels
|
||||
grep -rn "aria-label\|role=" Resources/Private/Templates/
|
||||
```
|
||||
|
||||
**Icon Registration:**
|
||||
```bash
|
||||
# Verify modern icon registration
|
||||
ls Configuration/Icons.php
|
||||
|
||||
# Check for deprecated IconRegistry (VIOLATION)
|
||||
grep -rn "IconRegistry" ext_localconf.php
|
||||
```
|
||||
|
||||
**Scoring Impact:**
|
||||
- Extension key consistency: -5 points if violations found
|
||||
- Inline JavaScript: -8 points (non-CSP-compliant)
|
||||
- Missing DocHeader: -4 points (poor UX)
|
||||
- Hardcoded URLs: -6 points (security risk)
|
||||
- No accessibility: -3 points (WCAG non-compliant)
|
||||
- Deprecated icon registration: -2 points
|
||||
|
||||
**Read `references/backend-module-v13.md` for:**
|
||||
- Complete before/after code examples
|
||||
- ES6 module architecture patterns
|
||||
- Modal/Notification API usage
|
||||
- WCAG 2.1 accessibility requirements
|
||||
- 10-phase modernization checklist
|
||||
- Real-world modernization case study (45/100 → 95/100)
|
||||
|
||||
---
|
||||
|
||||
### Step 4: PHP Architecture Evaluation
|
||||
|
||||
**Reference:** `references/php-architecture.md`
|
||||
|
||||
**Dependency Injection Analysis:**
|
||||
|
||||
```bash
|
||||
# Check for Services.yaml
|
||||
cat Configuration/Services.yaml
|
||||
|
||||
# Find constructors in Controllers/Services
|
||||
grep -A 10 "public function __construct" Classes/Controller/*.php
|
||||
grep -A 10 "public function __construct" Classes/Service/*.php
|
||||
```
|
||||
|
||||
**Check for:**
|
||||
- [ ] `Configuration/Services.yaml` present and properly configured
|
||||
- [ ] Constructor injection used (not GeneralUtility::makeInstance)
|
||||
- [ ] PSR-14 event listeners instead of hooks
|
||||
- [ ] Event classes properly structured (immutable with getters/setters)
|
||||
- [ ] PSR-15 middlewares if applicable
|
||||
- [ ] Extbase patterns (models, repositories, controllers)
|
||||
- [ ] No global state access ($GLOBALS)
|
||||
|
||||
**Anti-Pattern Detection:**
|
||||
```bash
|
||||
# Find deprecated patterns
|
||||
grep -r "GeneralUtility::makeInstance" Classes/
|
||||
grep -r "\$GLOBALS\[" Classes/
|
||||
grep -r "inject[A-Z]" Classes/ # Method injection (check if justified)
|
||||
```
|
||||
|
||||
**Output Format:**
|
||||
```markdown
|
||||
## PHP Architecture Conformance
|
||||
|
||||
### ✅ Modern Patterns
|
||||
- Dependency injection via constructor in all controllers
|
||||
- PSR-14 events used for extensibility
|
||||
- Configuration/Services.yaml properly configured
|
||||
- Extbase repositories extend base Repository class
|
||||
|
||||
### ❌ Architecture Issues
|
||||
- 12 instances of GeneralUtility::makeInstance()
|
||||
- Classes/Service/LegacyService.php:45
|
||||
- Classes/Utility/DatabaseHelper.php:89
|
||||
- 5 instances of $GLOBALS access
|
||||
- Classes/Controller/ProductController.php:123
|
||||
- No event listeners found (using deprecated hooks)
|
||||
|
||||
### 💡 Recommendations
|
||||
- Migrate hook implementations to PSR-14 events
|
||||
- Refactor GeneralUtility::makeInstance() to constructor injection
|
||||
- Remove global state dependencies
|
||||
```
|
||||
|
||||
### Step 5: Testing Infrastructure Assessment
|
||||
|
||||
**Reference:** `references/testing-standards.md`
|
||||
|
||||
**DELEGATION STRATEGY: For Deep Testing Analysis**
|
||||
|
||||
When Testing Standards category needs comprehensive validation, use skill delegation:
|
||||
|
||||
```
|
||||
🔧 Use /skill typo3-tests (if available) for deep analysis:
|
||||
- PHPUnit configuration quality and best practices
|
||||
- Test code patterns (AAA, proper mocking, fixtures)
|
||||
- TYPO3 Testing Framework usage validation
|
||||
- Functional test database handling
|
||||
- Accurate test coverage calculation
|
||||
- Test quality metrics and anti-patterns
|
||||
- Integration with TYPO3 core testing infrastructure
|
||||
|
||||
Return: Detailed testing conformance report with specific issues
|
||||
```
|
||||
|
||||
**Fallback: If typo3-tests skill unavailable, perform basic validation:**
|
||||
|
||||
**Test Coverage Analysis:**
|
||||
|
||||
```bash
|
||||
# Check test structure
|
||||
ls -R Tests/
|
||||
|
||||
# Verify PHPUnit configuration
|
||||
cat Build/phpunit/UnitTests.xml
|
||||
cat Build/phpunit/FunctionalTests.xml
|
||||
|
||||
# Count test files
|
||||
find Tests/Unit/ -name "*Test.php" | wc -l
|
||||
find Tests/Functional/ -name "*Test.php" | wc -l
|
||||
```
|
||||
|
||||
**Basic Evaluation Checklist:**
|
||||
- [ ] Tests/Unit/ mirrors Classes/ structure
|
||||
- [ ] Tests/Functional/ present with fixtures
|
||||
- [ ] PHPUnit configuration files present
|
||||
- [ ] Unit tests extend UnitTestCase
|
||||
- [ ] Functional tests extend FunctionalTestCase
|
||||
- [ ] Acceptance tests configured (Codeception)
|
||||
- [ ] Test coverage >70% for new code
|
||||
|
||||
**Note:** Basic validation provides surface-level checks. For production-ready conformance reports, delegate to typo3-tests skill for comprehensive analysis
|
||||
|
||||
**Sample Test Inspection:**
|
||||
```php
|
||||
# Read sample test files
|
||||
cat Tests/Unit/Service/CalculationServiceTest.php
|
||||
cat Tests/Functional/Domain/Repository/ProductRepositoryTest.php
|
||||
```
|
||||
|
||||
**Output Format:**
|
||||
```markdown
|
||||
## Testing Standards Conformance
|
||||
|
||||
### ✅ Test Infrastructure
|
||||
- PHPUnit configuration files present
|
||||
- Tests/ directory mirrors Classes/ structure
|
||||
- Unit tests properly extend UnitTestCase
|
||||
- Functional tests use fixtures correctly
|
||||
|
||||
### ❌ Testing Gaps
|
||||
- No Tests/Functional/ directory found
|
||||
- Missing PHPUnit configuration for functional tests
|
||||
- 15 classes without corresponding unit tests:
|
||||
- Classes/Service/EmailService.php
|
||||
- Classes/Utility/StringHelper.php
|
||||
- Classes/Domain/Repository/CategoryRepository.php
|
||||
|
||||
### 📊 Coverage Estimate
|
||||
- Unit test files: 12
|
||||
- Classes without tests: 15
|
||||
- Estimated coverage: ~45% (below 70% recommendation)
|
||||
```
|
||||
|
||||
### Step 6: Standards Application
|
||||
|
||||
**Reference:** `references/best-practices.md`
|
||||
|
||||
**When evaluating build scripts** (see `references/runtests-validation.md`):
|
||||
1. Verify Build/Scripts/runTests.sh exists and matches Tea extension reference
|
||||
2. Confirm PHP_VERSION default matches composer.json minimum requirement
|
||||
3. Validate TYPO3_VERSION default matches composer.json target version
|
||||
4. Check PHP version regex includes only supported versions (8.1-8.4)
|
||||
5. Ensure database version lists are current (no EOL versions)
|
||||
6. Verify network name is customized (not "friendsoftypo3-tea")
|
||||
7. Validate test suite paths match actual directory structure
|
||||
|
||||
**When evaluating development environment** (see `references/development-environment.md`):
|
||||
1. Locate DDEV configuration (.ddev/config.yaml) or Docker Compose alternative
|
||||
2. When DDEV present, verify type set to 'typo3'
|
||||
3. Confirm DDEV PHP version matches composer.json minimum
|
||||
4. Validate DDEV docroot matches composer.json web-dir
|
||||
5. Check database is MariaDB 10.11+ or MySQL 8.0+
|
||||
6. Flag missing DevContainer configuration as optional improvement
|
||||
|
||||
**When evaluating directory structure** (see `references/directory-structure.md`):
|
||||
1. Verify Build/ directory exists with committed configuration
|
||||
2. Confirm .Build/ is fully gitignored (entire directory)
|
||||
3. Flag any .Build/ files committed to git as critical violation
|
||||
4. Check cache files are in .Build/, not Build/
|
||||
5. Validate Composer paths reference .Build/ (bin-dir, vendor-dir, web-dir)
|
||||
6. Verify quality tool configs reference .Build/ for cache
|
||||
|
||||
**When evaluating project infrastructure**:
|
||||
1. Check .editorconfig presence for consistent code formatting
|
||||
2. Verify .gitignore properly configured with standard exclusions
|
||||
3. Locate CI/CD pipeline (.github/workflows/ or .gitlab-ci.yml)
|
||||
4. Confirm code quality tools configured (php-cs-fixer, phpstan)
|
||||
5. Validate README.md provides clear setup instructions
|
||||
6. Ensure LICENSE file present with appropriate open-source license
|
||||
7. Check GitHub issue templates (.github/ISSUE_TEMPLATE/config.yml)
|
||||
8. Verify TER publishing workflow (.github/workflows/publish-to-ter.yml)
|
||||
|
||||
**Security Practices:**
|
||||
```bash
|
||||
# Check for security issues
|
||||
grep -r "->exec(" Classes/ # SQL injection risk
|
||||
grep -r "htmlspecialchars" Resources/Private/Templates/ # XSS prevention
|
||||
grep -r "GeneralUtility::validEmail" Classes/ # Email validation
|
||||
```
|
||||
|
||||
**Documentation Quality:**
|
||||
|
||||
**DELEGATION STRATEGY: For Deep Documentation Analysis**
|
||||
|
||||
When Documentation Excellence validation is needed, use skill delegation:
|
||||
|
||||
```
|
||||
📚 Use /skill typo3-docs (if available) for deep analysis:
|
||||
- RST syntax validation and TYPO3 directive compliance
|
||||
- Documentation structure conformance (Index.rst, Settings.cfg)
|
||||
- TYPO3 documentation standards (guides.xml, screenshots.json)
|
||||
- Rendering validation with Docker (official TYPO3 render-guides)
|
||||
- Intersphinx references validation
|
||||
- Code example syntax validation
|
||||
- Cross-reference integrity
|
||||
- Modern documentation tooling detection
|
||||
|
||||
Return: Comprehensive documentation conformance report
|
||||
```
|
||||
|
||||
**Fallback: If typo3-docs skill unavailable, perform basic validation:**
|
||||
|
||||
```bash
|
||||
# Check documentation completeness
|
||||
ls -1 Documentation/ | wc -l
|
||||
cat Documentation/Index.rst | head -50
|
||||
|
||||
# Check for required files
|
||||
ls Documentation/Settings.cfg Documentation/guides.xml 2>/dev/null
|
||||
|
||||
# Count RST files for excellence scoring
|
||||
find Documentation/ -name "*.rst" | wc -l
|
||||
```
|
||||
|
||||
**Note:** Basic validation only checks file existence. For production-ready documentation conformance, delegate to typo3-docs skill for comprehensive RST validation and rendering checks
|
||||
|
||||
**Output Format:**
|
||||
```markdown
|
||||
## Best Practices Conformance
|
||||
|
||||
### ✅ Project Infrastructure
|
||||
- .editorconfig present
|
||||
- .gitignore configured properly
|
||||
- README.md with installation instructions
|
||||
- GitHub Actions CI/CD pipeline configured
|
||||
|
||||
### ❌ Missing Components
|
||||
- No phpstan.neon configuration
|
||||
- No .php-cs-fixer configuration
|
||||
- LICENSE file missing
|
||||
- Documentation/ incomplete (missing Developer/ section)
|
||||
|
||||
### 🔒 Security Review
|
||||
- ✅ Query parameters properly escaped
|
||||
- ⚠️ 3 instances of direct SQL queries (check for injection risks)
|
||||
- ✅ Email validation using GeneralUtility::validEmail
|
||||
- ❌ Missing CSRF tokens in 2 forms
|
||||
```
|
||||
|
||||
## Comprehensive Report Generation
|
||||
|
||||
### Final Conformance Report Template
|
||||
|
||||
```markdown
|
||||
# TYPO3 Extension Conformance Report
|
||||
|
||||
**Extension:** {extension_name} (v{version})
|
||||
**Evaluation Date:** {date}
|
||||
**TYPO3 Compatibility:** {typo3_versions}
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
**Base Conformance Score:** {score}/100
|
||||
**Excellence Indicators:** {excellence_score}/20 (Bonus)
|
||||
**Total Score:** {total_score}/120
|
||||
|
||||
### Base Conformance Breakdown (0-100 points)
|
||||
- Extension Architecture: {score}/20
|
||||
- Coding Guidelines: {score}/20
|
||||
- PHP Architecture: {score}/20
|
||||
- Testing Standards: {score}/20
|
||||
- Best Practices: {score}/20
|
||||
|
||||
### Excellence Indicators (0-20 bonus points)
|
||||
- Community & Internationalization: {score}/6
|
||||
- Advanced Quality Tooling: {score}/7
|
||||
- Documentation Excellence: {score}/4
|
||||
- Extension Configuration: {score}/3
|
||||
|
||||
**Priority Issues:** {count_critical}
|
||||
**Recommendations:** {count_recommendations}
|
||||
|
||||
---
|
||||
|
||||
## 1. Extension Architecture ({score}/20)
|
||||
|
||||
### ✅ Strengths
|
||||
- {list strengths}
|
||||
|
||||
### ❌ Critical Issues
|
||||
- {list critical issues with file:line references}
|
||||
|
||||
### ⚠️ Warnings
|
||||
- {list warnings}
|
||||
|
||||
### 💡 Recommendations
|
||||
1. {specific actionable recommendations}
|
||||
|
||||
---
|
||||
|
||||
## 2. Coding Guidelines ({score}/20)
|
||||
|
||||
{repeat same structure}
|
||||
|
||||
---
|
||||
|
||||
## 3. PHP Architecture ({score}/20)
|
||||
|
||||
{repeat same structure}
|
||||
|
||||
---
|
||||
|
||||
## 4. Testing Standards ({score}/20)
|
||||
|
||||
{repeat same structure}
|
||||
|
||||
---
|
||||
|
||||
## 5. Best Practices ({score}/20)
|
||||
|
||||
{repeat same structure}
|
||||
|
||||
---
|
||||
|
||||
## Priority Action Items
|
||||
|
||||
### High Priority (Fix Immediately)
|
||||
1. {critical issues that break functionality or security}
|
||||
|
||||
### Medium Priority (Fix Soon)
|
||||
1. {important conformance issues}
|
||||
|
||||
### Low Priority (Improve When Possible)
|
||||
1. {minor style and optimization issues}
|
||||
|
||||
---
|
||||
|
||||
## Detailed Issue List
|
||||
|
||||
| Category | Severity | File | Line | Issue | Recommendation |
|
||||
|----------|----------|------|------|-------|----------------|
|
||||
| Architecture | Critical | ext_tables.php | - | Using deprecated ext_tables.php | Migrate to Configuration/Backend/Modules.php |
|
||||
| Coding | High | Classes/Controller/ProductController.php | 12 | Missing declare(strict_types=1) | Add at top of file |
|
||||
| Architecture | High | Classes/Service/EmailService.php | 45 | Using GeneralUtility::makeInstance() | Switch to constructor injection |
|
||||
| Testing | Medium | Tests/ | - | No functional tests | Create Tests/Functional/ with fixtures |
|
||||
|
||||
---
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### Migrating from ext_tables.php to Configuration/Backend/
|
||||
|
||||
```php
|
||||
// Before (ext_tables.php) - DEPRECATED
|
||||
ExtensionUtility::registerModule(...);
|
||||
|
||||
// After (Configuration/Backend/Modules.php) - MODERN
|
||||
return [
|
||||
'web_myext' => [
|
||||
'parent' => 'web',
|
||||
...
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
### Converting to Constructor Injection
|
||||
|
||||
```php
|
||||
// Before - DEPRECATED
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
$repository = GeneralUtility::makeInstance(ProductRepository::class);
|
||||
|
||||
// After - MODERN
|
||||
public function __construct(
|
||||
private readonly ProductRepository $repository
|
||||
) {}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pre-Evaluation Validation Procedures
|
||||
|
||||
Execute these validation steps systematically during conformance evaluation:
|
||||
|
||||
**File Structure Validation**
|
||||
1. Verify composer.json exists with PSR-4 autoloading configuration
|
||||
2. Confirm Classes/ directory follows namespace hierarchy
|
||||
3. Check Configuration/ uses modern structure (no ext_tables.php dependencies)
|
||||
4. Validate Resources/ separates Private/ and Public/ correctly
|
||||
5. Ensure Tests/ mirrors Classes/ structure exactly
|
||||
6. Confirm Documentation/ contains Index.rst and Settings.cfg
|
||||
|
||||
**Coding Standards Validation**
|
||||
1. Scan all PHP files for declare(strict_types=1) at file start
|
||||
2. Verify type declarations on all properties, parameters, and return types
|
||||
3. Check PHPDoc blocks on all public methods and classes
|
||||
4. Run PSR-12 compliance validation via PHPCS
|
||||
5. Validate naming conventions (classes, methods, variables)
|
||||
|
||||
**PHP Architecture Validation**
|
||||
1. Verify constructor injection used throughout codebase
|
||||
2. Check Configuration/Services.yaml exists and configures services
|
||||
3. Confirm PSR-14 events replace deprecated hooks
|
||||
4. Search for GeneralUtility::makeInstance() usage (flag as violation)
|
||||
5. Search for $GLOBALS access (flag as violation)
|
||||
|
||||
**Testing Validation**
|
||||
1. Verify unit tests exist and execute successfully
|
||||
2. Check functional tests include proper fixtures
|
||||
3. Calculate test coverage (target >70%)
|
||||
4. Confirm PHPUnit configuration files present (UnitTests.xml, FunctionalTests.xml)
|
||||
5. Check acceptance tests if web interface present
|
||||
|
||||
**Standards Application Validation**
|
||||
1. Verify development environment configured (DDEV or Docker Compose)
|
||||
2. Check Build/Scripts/runTests.sh exists with accurate configuration
|
||||
3. Validate directory structure (.Build/ vs Build/) separation
|
||||
4. Confirm code quality tools configured (phpstan, php-cs-fixer)
|
||||
5. Verify CI/CD pipeline setup (.github/workflows/ or .gitlab-ci.yml)
|
||||
6. Review security practices implementation
|
||||
7. Validate documentation completeness
|
||||
8. Confirm README.md and LICENSE files present
|
||||
|
||||
---
|
||||
|
||||
## Reference Material Usage
|
||||
|
||||
**When checking extension architecture patterns**, read `references/extension-architecture.md` for:
|
||||
- Standard directory structures and naming conventions
|
||||
- Required files and their purposes
|
||||
- PSR-4 autoloading configuration examples
|
||||
|
||||
**When validating coding guidelines**, read `references/coding-guidelines.md` for:
|
||||
- PSR-12 compliance requirements
|
||||
- TYPO3-specific code style rules
|
||||
- Type declaration standards
|
||||
|
||||
**When evaluating backend modules**, read `references/backend-module-v13.md` for:
|
||||
- Extension key consistency validation
|
||||
- JavaScript modernization (ES6 modules, no inline scripts)
|
||||
- TYPO3 Modal and Notification API patterns
|
||||
- DocHeader integration (ButtonBar, IconFactory)
|
||||
- Module.html layout requirements
|
||||
- ARIA accessibility standards
|
||||
- Icon registration (Configuration/Icons.php)
|
||||
- CSRF protection via uriBuilder
|
||||
- 10-phase modernization checklist
|
||||
- Real-world case study (45/100 → 95/100 compliance)
|
||||
|
||||
**When evaluating PHP architecture**, read `references/php-architecture.md` for:
|
||||
- Dependency injection patterns
|
||||
- Service configuration examples
|
||||
- PSR-14 event system usage
|
||||
|
||||
**When analyzing testing standards**, read `references/testing-standards.md` for:
|
||||
- PHPUnit configuration patterns
|
||||
- Test structure requirements
|
||||
- Coverage calculation methods
|
||||
|
||||
**When applying best practices**, read `references/best-practices.md` for:
|
||||
- Development environment setup patterns
|
||||
- Build script validation criteria
|
||||
- Directory structure standards
|
||||
|
||||
**For build script validation**, read `references/runtests-validation.md` for:
|
||||
- Line-by-line runTests.sh validation
|
||||
- Tea extension reference comparison
|
||||
- Common configuration errors
|
||||
|
||||
**For development environment checks**, read `references/development-environment.md` for:
|
||||
- DDEV configuration standards
|
||||
- Docker Compose alternatives
|
||||
- PHP and database version requirements
|
||||
|
||||
**For directory structure evaluation**, read `references/directory-structure.md` for:
|
||||
- .Build/ vs Build/ separation rules
|
||||
- Composer path configuration
|
||||
- Gitignore patterns
|
||||
|
||||
**For excellence indicators**, read `references/excellence-indicators.md` for:
|
||||
- Crowdin integration validation
|
||||
- Advanced quality tooling detection
|
||||
- Documentation excellence criteria
|
||||
|
||||
**For Crowdin integration**, read `references/crowdin-integration.md` for:
|
||||
- TYPO3-compliant crowdin.yml patterns
|
||||
- Translation workflow validation
|
||||
- Common misconfigurations
|
||||
|
||||
**For hooks and events migration**, read `references/hooks-and-events.md` for:
|
||||
- PSR-14 event patterns
|
||||
- Deprecated hook identification
|
||||
- Migration strategies
|
||||
|
||||
**When encountering official documentation**, visit:
|
||||
- TYPO3 Core API: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/
|
||||
- Extension Architecture: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/
|
||||
- Coding Guidelines: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/CodingGuidelines/
|
||||
- Testing Documentation: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/Testing/
|
||||
- Tea Extension (Best Practice): https://github.com/TYPO3BestPractices/tea
|
||||
```
|
||||
|
||||
## Scoring System
|
||||
|
||||
### Overall Score Calculation
|
||||
|
||||
Each category (Architecture, Coding, PHP Architecture, Testing, Best Practices) is scored out of 20 points:
|
||||
|
||||
**Extension Architecture (20 points)**
|
||||
- Required files present: 8 points
|
||||
- Directory structure conformant: 6 points
|
||||
- Naming conventions followed: 4 points
|
||||
- No critical violations: 2 points
|
||||
|
||||
**Coding Guidelines (20 points)**
|
||||
- PSR-12 compliance: 8 points
|
||||
- Type declarations: 4 points
|
||||
- PHPDoc completeness: 4 points
|
||||
- Naming conventions: 4 points
|
||||
|
||||
**PHP Architecture (20 points)**
|
||||
- Dependency injection: 8 points
|
||||
- No deprecated patterns: 6 points
|
||||
- Modern event system: 4 points
|
||||
- Service configuration: 2 points
|
||||
|
||||
**Testing Standards (20 points)**
|
||||
- Test coverage >70%: 10 points
|
||||
- Proper test structure: 6 points
|
||||
- Configuration files present: 4 points
|
||||
|
||||
**Best Practices (20 points)**
|
||||
- Development environment (DDEV/Docker): 6 points
|
||||
- DDEV configuration present: 4 points
|
||||
- Configuration matches extension requirements: 2 points
|
||||
- OR Docker Compose alternative: 3 points
|
||||
- Build scripts (runTests.sh): 6 points
|
||||
- Script present and executable: 2 points
|
||||
- PHP/TYPO3 versions match extension: 3 points
|
||||
- Database versions current: 1 point
|
||||
- Directory structure (.Build/ vs Build/): 4 points
|
||||
- .Build/ properly gitignored: 2 points
|
||||
- Cache files in correct location: 1 point
|
||||
- Composer paths aligned: 1 point
|
||||
- Quality tools configured: 2 points
|
||||
- Documentation complete: 2 points
|
||||
|
||||
**Note:** Previously this category scored only quality tools (6) and documentation (4). The new comprehensive approach validates development environment setup, build script accuracy, and directory structure standards, providing more thorough conformance assessment.
|
||||
|
||||
### Excellence Indicators (Bonus 0-20 points)
|
||||
|
||||
**Reference:** `references/excellence-indicators.md`
|
||||
|
||||
Excellence indicators are **optional features** that demonstrate exceptional quality and community engagement. Extensions are NOT penalized for missing these features - they provide bonus points only.
|
||||
|
||||
**Total Possible Score: 122 points** (100 base conformance + 22 excellence bonus)
|
||||
|
||||
**Category 1: Community & Internationalization (0-6 points)**
|
||||
- Crowdin integration: 0-2 points
|
||||
- Basic (crowdin.yml exists): +1 point
|
||||
- TYPO3-compliant (preserve_hierarchy, wildcards, proper patterns, no download job): +2 points
|
||||
- See `references/crowdin-integration.md` for comprehensive validation
|
||||
- GitHub issue templates (.github/ISSUE_TEMPLATE/): +1 point
|
||||
- .gitattributes with export-ignore: +1 point
|
||||
- Professional README badges (stability, versions, downloads, CI): +2 points
|
||||
|
||||
**Category 2: Advanced Quality Tooling (0-9 points)**
|
||||
- Fractor configuration (Build/fractor/fractor.php): +2 points
|
||||
- TYPO3 CodingStandards package (typo3/coding-standards in composer.json): +2 points
|
||||
- StyleCI integration (.styleci.yml): +1 point
|
||||
- Makefile with self-documenting help: +1 point
|
||||
- CI testing matrix (multiple PHP/TYPO3 versions): +1 point
|
||||
- TER publishing workflow (.github/workflows/publish-to-ter.yml): +2 points
|
||||
|
||||
**Category 3: Documentation Excellence (0-4 points)**
|
||||
- 50-99 RST files in Documentation/: +1 point
|
||||
- 100-149 RST files: +2 points
|
||||
- 150+ RST files: +3 points
|
||||
- Modern documentation tooling (guides.xml, screenshots.json): +1 point
|
||||
|
||||
**Category 4: Extension Configuration (0-3 points)**
|
||||
- ext_conf_template.txt with proper categorization: +1 point
|
||||
- Composer documentation scripts (doc-init, doc-make, doc-watch): +1 point
|
||||
- Multiple Configuration/Sets/ presets (for different use cases): +1 point
|
||||
|
||||
**Excellence Score Interpretation:**
|
||||
- **0-5 points:** Standard extension (meets requirements)
|
||||
- **6-11 points:** Good practices (actively maintained)
|
||||
- **12-16 points:** Excellent quality (community reference level)
|
||||
- **17-22 points:** Outstanding (georgringer/news level)
|
||||
|
||||
**Example Report Format:**
|
||||
|
||||
```markdown
|
||||
## TYPO3 Extension Conformance Report
|
||||
|
||||
**Extension:** my_extension (v2.0.0)
|
||||
|
||||
---
|
||||
|
||||
### Score Summary
|
||||
|
||||
**Base Conformance:** 94/100
|
||||
- Extension Architecture: 18/20
|
||||
- Coding Guidelines: 20/20
|
||||
- PHP Architecture: 18/20
|
||||
- Testing Standards: 18/20
|
||||
- Best Practices: 20/20
|
||||
|
||||
**Excellence Indicators:** 14/22 (Bonus)
|
||||
- Community & Internationalization: 5/6
|
||||
- ✅ Crowdin integration (+2)
|
||||
- ✅ Professional README badges (+2)
|
||||
- ✅ GitHub issue templates (+1)
|
||||
- ❌ No .gitattributes export-ignore
|
||||
|
||||
- Advanced Quality Tooling: 7/9
|
||||
- ✅ Fractor configuration (+2)
|
||||
- ✅ TYPO3 CodingStandards (+2)
|
||||
- ✅ Makefile with help (+1)
|
||||
- ✅ TER publishing workflow (+2)
|
||||
- ❌ No StyleCI
|
||||
- ❌ No CI testing matrix
|
||||
|
||||
- Documentation Excellence: 2/4
|
||||
- ✅ 75 RST files (+1)
|
||||
- ✅ Modern tooling (guides.xml) (+1)
|
||||
|
||||
- Extension Configuration: 1/3
|
||||
- ✅ Composer doc scripts (+1)
|
||||
- ❌ No ext_conf_template.txt
|
||||
- ❌ Only one Configuration/Sets/ preset
|
||||
|
||||
**Total Score:** 108/122
|
||||
|
||||
**Rating:** Excellent - This extension demonstrates strong conformance and excellent quality practices.
|
||||
```
|
||||
|
||||
**Important Notes:**
|
||||
- Base conformance (0-100) is MANDATORY - this is pass/fail criteria
|
||||
- Excellence indicators (0-22) are OPTIONAL - bonus points for exceptional quality
|
||||
- Extensions scoring 100/100 base are fully conformant, regardless of excellence score
|
||||
- Excellence indicators identify community reference extensions
|
||||
|
||||
**📚 When Documentation/ is Missing or Incomplete:**
|
||||
|
||||
If conformance check reveals missing or incomplete documentation (0-1 points in Documentation Excellence):
|
||||
|
||||
**Recommended Next Step:**
|
||||
```
|
||||
📚 Use typo3-docs skill to create comprehensive documentation:
|
||||
- Creates complete Documentation/ structure (8 sections)
|
||||
- Follows modern TYPO3 13.x standards
|
||||
- Uses card-grid navigation (not plain toctree)
|
||||
- Applies confval directives for configuration
|
||||
- Includes Settings.cfg and proper RST structure
|
||||
- Ready for docs.typo3.org deployment
|
||||
|
||||
After creation: Re-run conformance check to verify improvement
|
||||
```
|
||||
|
||||
**Documentation is the most common conformance gap** - extensions often score 90-95/100 with 0/4 documentation. Creating proper documentation typically improves score to 94-99/100 base + 1-2/20 excellence.
|
||||
|
||||
### Severity Levels
|
||||
|
||||
**Critical (Blocker):**
|
||||
- Security vulnerabilities
|
||||
- Broken functionality
|
||||
- Major architecture violations
|
||||
|
||||
**High (Must Fix):**
|
||||
- Deprecated pattern usage
|
||||
- Missing required files
|
||||
- Significant PSR-12 violations
|
||||
|
||||
**Medium (Should Fix):**
|
||||
- Missing tests
|
||||
- Incomplete documentation
|
||||
- Minor architecture issues
|
||||
|
||||
**Low (Nice to Have):**
|
||||
- Code style inconsistencies
|
||||
- Optional quality improvements
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Example 1: Quick Conformance Check
|
||||
|
||||
```
|
||||
User: "Check if my TYPO3 extension follows current standards"
|
||||
1059
skills/typo3-conformance/references/backend-module-v13.md
Normal file
1059
skills/typo3-conformance/references/backend-module-v13.md
Normal file
File diff suppressed because it is too large
Load Diff
917
skills/typo3-conformance/references/best-practices.md
Normal file
917
skills/typo3-conformance/references/best-practices.md
Normal file
@@ -0,0 +1,917 @@
|
||||
# TYPO3 Extension Best Practices
|
||||
|
||||
**Source:** TYPO3 Best Practices (Tea Extension) and Core API Standards
|
||||
**Purpose:** Real-world patterns and organizational best practices for TYPO3 extensions
|
||||
|
||||
## Project Structure
|
||||
|
||||
### Complete Extension Layout
|
||||
|
||||
```
|
||||
my_extension/
|
||||
├── .ddev/ # DDEV configuration
|
||||
│ └── config.yaml
|
||||
├── .github/ # GitHub Actions CI/CD
|
||||
│ └── workflows/
|
||||
│ └── tests.yml
|
||||
├── Build/ # Build tools and configs
|
||||
│ ├── phpunit/
|
||||
│ │ ├── UnitTests.xml
|
||||
│ │ └── FunctionalTests.xml
|
||||
│ └── Scripts/
|
||||
│ └── runTests.sh
|
||||
├── Classes/ # PHP source code
|
||||
│ ├── Controller/
|
||||
│ ├── Domain/
|
||||
│ │ ├── Model/
|
||||
│ │ └── Repository/
|
||||
│ ├── Service/
|
||||
│ ├── Utility/
|
||||
│ ├── EventListener/
|
||||
│ └── ViewHelper/
|
||||
├── Configuration/ # TYPO3 configuration
|
||||
│ ├── Backend/
|
||||
│ │ └── Modules.php
|
||||
│ ├── Services.yaml
|
||||
│ ├── TCA/
|
||||
│ ├── TypoScript/
|
||||
│ │ ├── setup.typoscript
|
||||
│ │ └── constants.typoscript
|
||||
│ └── Sets/ # TYPO3 v13+
|
||||
│ └── MySet/
|
||||
│ └── config.yaml
|
||||
├── Documentation/ # RST documentation
|
||||
│ ├── Index.rst
|
||||
│ ├── Settings.cfg
|
||||
│ ├── Introduction/
|
||||
│ ├── Installation/
|
||||
│ ├── Configuration/
|
||||
│ ├── Developer/
|
||||
│ └── Editor/
|
||||
├── Resources/
|
||||
│ ├── Private/
|
||||
│ │ ├── Language/
|
||||
│ │ │ ├── locallang.xlf
|
||||
│ │ │ └── de.locallang.xlf
|
||||
│ │ ├── Layouts/
|
||||
│ │ ├── Partials/
|
||||
│ │ └── Templates/
|
||||
│ └── Public/
|
||||
│ ├── Css/
|
||||
│ ├── Icons/
|
||||
│ ├── Images/
|
||||
│ └── JavaScript/
|
||||
├── Tests/
|
||||
│ ├── Unit/
|
||||
│ ├── Functional/
|
||||
│ │ └── Fixtures/
|
||||
│ └── Acceptance/
|
||||
│ ├── Support/
|
||||
│ └── codeception.yml
|
||||
├── .editorconfig # Editor configuration
|
||||
├── .gitattributes # Git attributes
|
||||
├── .gitignore # Git ignore rules
|
||||
├── .php-cs-fixer.dist.php # PHP CS Fixer config
|
||||
├── composer.json # Composer configuration
|
||||
├── composer.lock # Locked dependencies
|
||||
├── ext_emconf.php # Extension metadata
|
||||
├── ext_localconf.php # Global configuration
|
||||
├── LICENSE # License file
|
||||
├── phpstan.neon # PHPStan configuration
|
||||
└── README.md # Project README
|
||||
```
|
||||
|
||||
## Best Practices by Category
|
||||
|
||||
### 1. Dependency Management
|
||||
|
||||
**composer.json Best Practices:**
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "vendor/my-extension",
|
||||
"type": "typo3-cms-extension",
|
||||
"description": "Clear, concise extension description",
|
||||
"license": "GPL-2.0-or-later",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Author Name",
|
||||
"email": "author@example.com",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"typo3/cms-core": "^12.4 || ^13.0",
|
||||
"typo3/cms-backend": "^12.4 || ^13.0",
|
||||
"typo3/cms-extbase": "^12.4 || ^13.0",
|
||||
"typo3/cms-fluid": "^12.4 || ^13.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"typo3/coding-standards": "^0.7",
|
||||
"typo3/testing-framework": "^8.0",
|
||||
"phpunit/phpunit": "^10.5",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"friendsofphp/php-cs-fixer": "^3.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Vendor\\MyExtension\\": "Classes/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Vendor\\MyExtension\\Tests\\": "Tests/"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"vendor-dir": ".Build/vendor",
|
||||
"bin-dir": ".Build/bin",
|
||||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
"typo3/class-alias-loader": true,
|
||||
"typo3/cms-composer-installers": true
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"typo3/cms": {
|
||||
"extension-key": "my_extension",
|
||||
"web-dir": ".Build/Web"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Code Quality Tools
|
||||
|
||||
**.php-cs-fixer.dist.php:**
|
||||
|
||||
```php
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
$config = \TYPO3\CodingStandards\CsFixerConfig::create();
|
||||
$config->getFinder()
|
||||
->in(__DIR__ . '/Classes')
|
||||
->in(__DIR__ . '/Configuration')
|
||||
->in(__DIR__ . '/Tests');
|
||||
|
||||
return $config;
|
||||
```
|
||||
|
||||
**phpstan.neon:**
|
||||
|
||||
```neon
|
||||
includes:
|
||||
- .Build/vendor/phpstan/phpstan/conf/bleedingEdge.neon
|
||||
|
||||
parameters:
|
||||
level: 9
|
||||
paths:
|
||||
- Classes
|
||||
- Configuration
|
||||
- Tests
|
||||
excludePaths:
|
||||
- .Build
|
||||
- vendor
|
||||
```
|
||||
|
||||
#### PHPStan Level 10 Best Practices for TYPO3
|
||||
|
||||
**Handling $GLOBALS['TCA'] in Tests:**
|
||||
|
||||
PHPStan cannot infer types for runtime-configured `$GLOBALS` arrays. Use ignore annotations:
|
||||
|
||||
```php
|
||||
// ✅ Right: Suppress offsetAccess warnings for $GLOBALS['TCA']
|
||||
/** @var array<string, mixed> $tcaConfig */
|
||||
$tcaConfig = [
|
||||
'type' => 'text',
|
||||
'enableRichtext' => true,
|
||||
];
|
||||
// @phpstan-ignore-next-line offsetAccess.nonOffsetAccessible
|
||||
$GLOBALS['TCA']['tt_content']['columns']['bodytext']['config'] = $tcaConfig;
|
||||
|
||||
// ❌ Wrong: No type annotation or suppression
|
||||
$GLOBALS['TCA']['tt_content']['columns']['bodytext']['config'] = [
|
||||
'type' => 'text',
|
||||
]; // PHPStan error: offsetAccess.nonOffsetAccessible
|
||||
```
|
||||
|
||||
**Factory Methods vs Property Initialization:**
|
||||
|
||||
Avoid uninitialized property errors in test classes:
|
||||
|
||||
```php
|
||||
// ❌ Wrong: PHPStan warns about uninitialized property
|
||||
final class MyServiceTest extends UnitTestCase
|
||||
{
|
||||
private MyService $subject; // Uninitialized property
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->subject = new MyService();
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Right: Use factory method
|
||||
final class MyServiceTest extends UnitTestCase
|
||||
{
|
||||
private function createSubject(): MyService
|
||||
{
|
||||
return new MyService();
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function testSomething(): void
|
||||
{
|
||||
$subject = $this->createSubject();
|
||||
// Use $subject
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Type Assertions for Dynamic Arrays:**
|
||||
|
||||
When testing arrays modified by reference:
|
||||
|
||||
```php
|
||||
// ❌ Wrong: PHPStan cannot verify type after modification
|
||||
public function testFieldProcessing(): void
|
||||
{
|
||||
$fieldArray = ['bodytext' => '<p>Test</p>'];
|
||||
$this->subject->processFields($fieldArray);
|
||||
|
||||
// PHPStan error: Cannot access offset on mixed
|
||||
self::assertStringContainsString('Test', $fieldArray['bodytext']);
|
||||
}
|
||||
|
||||
// ✅ Right: Add type assertions
|
||||
public function testFieldProcessing(): void
|
||||
{
|
||||
$fieldArray = ['bodytext' => '<p>Test</p>'];
|
||||
$this->subject->processFields($fieldArray);
|
||||
|
||||
self::assertArrayHasKey('bodytext', $fieldArray);
|
||||
self::assertIsString($fieldArray['bodytext']);
|
||||
self::assertStringContainsString('Test', $fieldArray['bodytext']);
|
||||
}
|
||||
```
|
||||
|
||||
**Intersection Types for Mocks:**
|
||||
|
||||
Use intersection types for proper PHPStan analysis of mocks:
|
||||
|
||||
```php
|
||||
// ✅ Right: Intersection type for mock
|
||||
/** @var ResourceFactory&MockObject $resourceFactoryMock */
|
||||
$resourceFactoryMock = $this->createMock(ResourceFactory::class);
|
||||
|
||||
// Alternative: @phpstan-var annotation
|
||||
$resourceFactoryMock = $this->createMock(ResourceFactory::class);
|
||||
/** @phpstan-var ResourceFactory&MockObject $resourceFactoryMock */
|
||||
```
|
||||
|
||||
**Common PHPStan Suppressions for TYPO3:**
|
||||
|
||||
```php
|
||||
// Suppress $GLOBALS['TCA'] access
|
||||
// @phpstan-ignore-next-line offsetAccess.nonOffsetAccessible
|
||||
$GLOBALS['TCA']['table']['columns']['field'] = $config;
|
||||
|
||||
// Suppress $GLOBALS['TYPO3_CONF_VARS'] access
|
||||
// @phpstan-ignore-next-line offsetAccess.nonOffsetAccessible
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['key'] = MyClass::class;
|
||||
|
||||
// Suppress mixed type from legacy code
|
||||
// @phpstan-ignore-next-line argument.type
|
||||
$this->view->assign('data', $legacyArray);
|
||||
```
|
||||
|
||||
**Type Hints for Service Container Retrieval:**
|
||||
|
||||
```php
|
||||
// ✅ Right: Type hint service retrieval
|
||||
/** @var DataHandler $dataHandler */
|
||||
$dataHandler = $this->get(DataHandler::class);
|
||||
|
||||
/** @var ResourceFactory $resourceFactory */
|
||||
$resourceFactory = $this->get(ResourceFactory::class);
|
||||
```
|
||||
|
||||
### 3. Service Configuration
|
||||
|
||||
**Configuration/Services.yaml:**
|
||||
|
||||
```yaml
|
||||
services:
|
||||
_defaults:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
public: false
|
||||
|
||||
# Auto-register all classes
|
||||
Vendor\MyExtension\:
|
||||
resource: '../Classes/*'
|
||||
|
||||
# Exclude specific directories
|
||||
Vendor\MyExtension\Domain\Model\:
|
||||
resource: '../Classes/Domain/Model/*'
|
||||
autoconfigure: false
|
||||
|
||||
# Explicit service configuration example
|
||||
Vendor\MyExtension\Service\EmailService:
|
||||
arguments:
|
||||
$fromEmail: '%env(DEFAULT_FROM_EMAIL)%'
|
||||
$fromName: 'TYPO3 Extension'
|
||||
|
||||
# Tag configuration example
|
||||
Vendor\MyExtension\Command\ImportCommand:
|
||||
tags:
|
||||
- name: 'console.command'
|
||||
command: 'myext:import'
|
||||
description: 'Import data from external source'
|
||||
```
|
||||
|
||||
### 4. Backend Module Configuration
|
||||
|
||||
**Configuration/Backend/Modules.php:**
|
||||
|
||||
```php
|
||||
<?php
|
||||
return [
|
||||
'web_myext' => [
|
||||
'parent' => 'web',
|
||||
'position' => ['after' => 'web_info'],
|
||||
'access' => 'user',
|
||||
'workspaces' => 'live',
|
||||
'path' => '/module/web/myext',
|
||||
'labels' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_mod.xlf',
|
||||
'extensionName' => 'MyExtension',
|
||||
'controllerActions' => [
|
||||
\Vendor\MyExtension\Controller\BackendController::class => [
|
||||
'list',
|
||||
'show',
|
||||
'edit',
|
||||
'update',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
### 5. Testing Infrastructure
|
||||
|
||||
**Build/Scripts/runTests.sh:**
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||
|
||||
# Run unit tests
|
||||
if [ "$1" = "unit" ]; then
|
||||
php vendor/bin/phpunit -c Build/phpunit/UnitTests.xml
|
||||
fi
|
||||
|
||||
# Run functional tests
|
||||
if [ "$1" = "functional" ]; then
|
||||
typo3DatabaseDriver=pdo_sqlite \
|
||||
php vendor/bin/phpunit -c Build/phpunit/FunctionalTests.xml
|
||||
fi
|
||||
|
||||
# Run all tests
|
||||
if [ "$1" = "all" ]; then
|
||||
php vendor/bin/phpunit -c Build/phpunit/UnitTests.xml
|
||||
typo3DatabaseDriver=pdo_sqlite \
|
||||
php vendor/bin/phpunit -c Build/phpunit/FunctionalTests.xml
|
||||
fi
|
||||
```
|
||||
|
||||
### 6. CI/CD Configuration
|
||||
|
||||
**.github/workflows/tests.yml:**
|
||||
|
||||
```yaml
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: Tests (PHP ${{ matrix.php }}, TYPO3 ${{ matrix.typo3 }})
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: ['8.1', '8.2', '8.3']
|
||||
typo3: ['12.4', '13.0']
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
extensions: mbstring, xml, json, zip, curl
|
||||
coverage: none
|
||||
|
||||
- name: Get Composer Cache Directory
|
||||
id: composer-cache
|
||||
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache Composer dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: ${{ runner.os }}-composer-
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --prefer-dist --no-progress
|
||||
|
||||
- name: Lint PHP
|
||||
run: find . -name \*.php ! -path "./vendor/*" ! -path "./.Build/*" -exec php -l {} \;
|
||||
|
||||
- name: PHP CS Fixer
|
||||
run: .Build/bin/php-cs-fixer fix --dry-run --diff
|
||||
|
||||
- name: PHPStan
|
||||
run: .Build/bin/phpstan analyze
|
||||
|
||||
- name: Unit Tests
|
||||
run: .Build/bin/phpunit -c Build/phpunit/UnitTests.xml
|
||||
|
||||
- name: Functional Tests
|
||||
run: |
|
||||
typo3DatabaseDriver=pdo_sqlite \
|
||||
.Build/bin/phpunit -c Build/phpunit/FunctionalTests.xml
|
||||
```
|
||||
|
||||
### 7. Documentation Standards
|
||||
|
||||
**Documentation/Index.rst:**
|
||||
|
||||
```rst
|
||||
.. include:: /Includes.rst.txt
|
||||
|
||||
==============
|
||||
My Extension
|
||||
==============
|
||||
|
||||
:Extension key:
|
||||
my_extension
|
||||
|
||||
:Package name:
|
||||
vendor/my-extension
|
||||
|
||||
:Version:
|
||||
|release|
|
||||
|
||||
:Language:
|
||||
en
|
||||
|
||||
:Author:
|
||||
Author Name
|
||||
|
||||
:License:
|
||||
This document is published under the
|
||||
`Creative Commons BY 4.0 <https://creativecommons.org/licenses/by/4.0/>`__
|
||||
license.
|
||||
|
||||
:Rendered:
|
||||
|today|
|
||||
|
||||
----
|
||||
|
||||
Clear and concise extension description explaining the purpose and main features.
|
||||
|
||||
----
|
||||
|
||||
**Table of Contents:**
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:titlesonly:
|
||||
|
||||
Introduction/Index
|
||||
Installation/Index
|
||||
Configuration/Index
|
||||
Editor/Index
|
||||
Developer/Index
|
||||
Sitemap
|
||||
```
|
||||
|
||||
**Page Size Guidelines:**
|
||||
|
||||
Follow TYPO3 documentation best practices for page organization and sizing:
|
||||
|
||||
**Index.rst (Landing Page):**
|
||||
- **Target:** 80-150 lines
|
||||
- **Maximum:** 200 lines
|
||||
- **Purpose:** Entry point with metadata, brief description, and navigation only
|
||||
- **Contains:** Extension metadata, brief description, card-grid (optional), toctree, license
|
||||
- **Anti-pattern:** ❌ Embedding all content (introduction, requirements, contributing, credits, etc.)
|
||||
|
||||
**Content Pages:**
|
||||
- **Target:** 100-300 lines per file
|
||||
- **Optimal:** 150-200 lines
|
||||
- **Maximum:** 400 lines (split if larger)
|
||||
- **Structure:** Focused on single topic or logically related concepts
|
||||
- **Split Strategy:** Create subdirectories for complex topics with multiple aspects
|
||||
|
||||
**Red Flags:**
|
||||
- ❌ Index.rst >200 lines → Extract content to Introduction/, Contributing/, etc.
|
||||
- ❌ Single file >400 lines → Split into multiple focused pages
|
||||
- ❌ All content in Index.rst → Create proper section directories
|
||||
- ❌ Navigation by scrolling → Use card-grid + toctree structure
|
||||
|
||||
**Proper Structure Example:**
|
||||
```
|
||||
Documentation/
|
||||
├── Index.rst # Landing page (80-150 lines)
|
||||
├── Introduction/ # Getting started
|
||||
│ └── Index.rst # Features, requirements, quick start
|
||||
├── Installation/ # Setup instructions
|
||||
│ └── Index.rst
|
||||
├── Configuration/ # Configuration guides
|
||||
│ ├── Index.rst
|
||||
│ ├── Basic.rst
|
||||
│ └── Advanced.rst
|
||||
├── Contributing/ # Contribution guidelines
|
||||
│ └── Index.rst # Code, translations, credits, resources
|
||||
├── Examples/ # Usage examples
|
||||
├── Troubleshooting/ # Problem solving
|
||||
└── API/ # Developer reference
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- ✅ Better user experience (focused, scannable pages)
|
||||
- ✅ Easier maintenance (smaller, manageable files)
|
||||
- ✅ Improved search results (specific pages rank better)
|
||||
- ✅ Clear information architecture
|
||||
- ✅ Follows TYPO3 documentation standards
|
||||
- ✅ Mobile-friendly navigation
|
||||
|
||||
**Reference:** [TYPO3 tea extension](https://github.com/TYPO3BestPractices/tea) - exemplary documentation structure
|
||||
|
||||
### 8. Version Control Best Practices
|
||||
|
||||
#### Default Branch Naming
|
||||
|
||||
**✅ Use `main` as the default branch instead of `master`**
|
||||
|
||||
**Rationale:**
|
||||
- **Industry Standard**: GitHub, GitLab, and Bitbucket all default to `main` for new repositories
|
||||
- **Modern Convention**: Aligns with current version control ecosystem standards
|
||||
- **Inclusive Language**: Part of broader industry shift toward inclusive terminology
|
||||
- **Consistency**: Matches TYPO3 Core and most modern TYPO3 extensions
|
||||
|
||||
**Migration from `master` to `main`:**
|
||||
|
||||
If your extension currently uses `master`, migrate to `main`:
|
||||
|
||||
```bash
|
||||
# 1. Create main branch from master
|
||||
git checkout master
|
||||
git pull origin master
|
||||
git checkout -b main
|
||||
git push -u origin main
|
||||
|
||||
# 2. Change default branch on GitHub
|
||||
gh repo edit --default-branch main
|
||||
|
||||
# 3. Update all branch references in codebase
|
||||
# - CI/CD workflows (.github/workflows/*.yml)
|
||||
# - Documentation (guides.xml, *.rst files)
|
||||
# - URLs in CONTRIBUTING.md, README.md
|
||||
|
||||
# 4. Delete old master branch
|
||||
git branch -d master
|
||||
git push origin --delete master
|
||||
```
|
||||
|
||||
**Example CI/CD workflow update:**
|
||||
|
||||
```yaml
|
||||
# .github/workflows/tests.yml
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop] # Changed from: master
|
||||
pull_request:
|
||||
branches: [main] # Changed from: master
|
||||
```
|
||||
|
||||
**Example documentation update:**
|
||||
|
||||
```xml
|
||||
<!-- Documentation/guides.xml -->
|
||||
<extension edit-on-github-branch="main" /> <!-- Changed from: master -->
|
||||
```
|
||||
|
||||
#### Branch Protection Enforcement
|
||||
|
||||
**Prevent accidental `master` branch recreation** and **protect `main` branch** using GitHub Repository Rulesets.
|
||||
|
||||
**Block master branch - prevents creation and pushes:**
|
||||
|
||||
Create `ruleset-block-master.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Block master branch",
|
||||
"target": "branch",
|
||||
"enforcement": "active",
|
||||
"conditions": {
|
||||
"ref_name": {
|
||||
"include": ["refs/heads/master"],
|
||||
"exclude": []
|
||||
}
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"type": "creation"
|
||||
},
|
||||
{
|
||||
"type": "update"
|
||||
},
|
||||
{
|
||||
"type": "deletion"
|
||||
}
|
||||
],
|
||||
"bypass_actors": []
|
||||
}
|
||||
```
|
||||
|
||||
Apply the ruleset:
|
||||
|
||||
```bash
|
||||
gh api -X POST repos/OWNER/REPO/rulesets \
|
||||
--input ruleset-block-master.json
|
||||
```
|
||||
|
||||
**Protect main branch - requires CI and prevents force pushes:**
|
||||
|
||||
Create `ruleset-protect-main.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Protect main branch",
|
||||
"target": "branch",
|
||||
"enforcement": "active",
|
||||
"conditions": {
|
||||
"ref_name": {
|
||||
"include": ["refs/heads/main"],
|
||||
"exclude": []
|
||||
}
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"type": "required_status_checks",
|
||||
"parameters": {
|
||||
"required_status_checks": [
|
||||
{
|
||||
"context": "build"
|
||||
}
|
||||
],
|
||||
"strict_required_status_checks_policy": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "non_fast_forward"
|
||||
}
|
||||
],
|
||||
"bypass_actors": [
|
||||
{
|
||||
"actor_id": 5,
|
||||
"actor_type": "RepositoryRole",
|
||||
"bypass_mode": "always"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Apply the ruleset:
|
||||
|
||||
```bash
|
||||
gh api -X POST repos/OWNER/REPO/rulesets \
|
||||
--input ruleset-protect-main.json
|
||||
```
|
||||
|
||||
**Verify rulesets are active:**
|
||||
|
||||
```bash
|
||||
# List all rulesets
|
||||
gh api repos/OWNER/REPO/rulesets
|
||||
|
||||
# Test master branch is blocked (should fail)
|
||||
git push origin test-branch:master
|
||||
# Expected: remote: error: GH013: Repository rule violations found
|
||||
```
|
||||
|
||||
**Benefits of Repository Rulesets:**
|
||||
- ✅ Prevents accidental `master` branch recreation
|
||||
- ✅ Enforces CI status checks before merging to `main`
|
||||
- ✅ Prevents force pushes to protected branches
|
||||
- ✅ Allows admin bypass for emergency situations
|
||||
- ✅ More flexible than legacy branch protection rules
|
||||
- ✅ Supports complex conditions and multiple rule types
|
||||
|
||||
### 9. Language File Organization
|
||||
|
||||
**Resources/Private/Language/locallang.xlf:**
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<file source-language="en" datatype="plaintext"
|
||||
original="EXT:my_extension/Resources/Private/Language/locallang.xlf"
|
||||
date="2024-01-01T12:00:00Z"
|
||||
product-name="my_extension">
|
||||
<header/>
|
||||
<body>
|
||||
<trans-unit id="plugin.title" resname="plugin.title">
|
||||
<source>My Extension Plugin</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="plugin.description" resname="plugin.description">
|
||||
<source>Displays product list with filters</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
```
|
||||
|
||||
### 10. TCA Best Practices
|
||||
|
||||
**Configuration/TCA/tx_myext_domain_model_product.php:**
|
||||
|
||||
```php
|
||||
<?php
|
||||
return [
|
||||
'ctrl' => [
|
||||
'title' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_product',
|
||||
'label' => 'title',
|
||||
'tstamp' => 'tstamp',
|
||||
'crdate' => 'crdate',
|
||||
'delete' => 'deleted',
|
||||
'sortby' => 'sorting',
|
||||
'versioningWS' => true,
|
||||
'origUid' => 't3_origuid',
|
||||
'languageField' => 'sys_language_uid',
|
||||
'transOrigPointerField' => 'l10n_parent',
|
||||
'transOrigDiffSourceField' => 'l10n_diffsource',
|
||||
'translationSource' => 'l10n_source',
|
||||
'enablecolumns' => [
|
||||
'disabled' => 'hidden',
|
||||
'starttime' => 'starttime',
|
||||
'endtime' => 'endtime',
|
||||
],
|
||||
'searchFields' => 'title,description',
|
||||
'iconfile' => 'EXT:my_extension/Resources/Public/Icons/product.svg',
|
||||
],
|
||||
'types' => [
|
||||
'1' => [
|
||||
'showitem' => '
|
||||
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general,
|
||||
title, description,
|
||||
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access,
|
||||
hidden, starttime, endtime
|
||||
',
|
||||
],
|
||||
],
|
||||
'columns' => [
|
||||
'title' => [
|
||||
'label' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_product.title',
|
||||
'config' => [
|
||||
'type' => 'input',
|
||||
'size' => 30,
|
||||
'eval' => 'trim,required',
|
||||
'max' => 255,
|
||||
],
|
||||
],
|
||||
'description' => [
|
||||
'label' => 'LLL:EXT:my_extension/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_product.description',
|
||||
'config' => [
|
||||
'type' => 'text',
|
||||
'enableRichtext' => true,
|
||||
'richtextConfiguration' => 'default',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
### 11. Security Best Practices
|
||||
|
||||
**✅ Input Validation:**
|
||||
```php
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
// Validate integer input
|
||||
if (!MathUtility::canBeInterpretedAsInteger($input)) {
|
||||
throw new \InvalidArgumentException('Invalid integer value');
|
||||
}
|
||||
|
||||
// Sanitize email
|
||||
$email = GeneralUtility::validEmail($input) ? $input : '';
|
||||
|
||||
// Escape output in templates
|
||||
{product.title -> f:format.htmlspecialchars()}
|
||||
```
|
||||
|
||||
**✅ SQL Injection Prevention:**
|
||||
```php
|
||||
// Use QueryBuilder with bound parameters
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getQueryBuilderForTable('tx_myext_domain_model_product');
|
||||
|
||||
$products = $queryBuilder
|
||||
->select('*')
|
||||
->from('tx_myext_domain_model_product')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'uid',
|
||||
$queryBuilder->createNamedParameter($uid, Connection::PARAM_INT)
|
||||
)
|
||||
)
|
||||
->executeQuery()
|
||||
->fetchAllAssociative();
|
||||
```
|
||||
|
||||
**✅ CSRF Protection:**
|
||||
```html
|
||||
<!-- Always include form protection token -->
|
||||
<f:form.hidden property="__trustedProperties" value="{formProtection}" />
|
||||
```
|
||||
|
||||
## Common Anti-Patterns to Avoid
|
||||
|
||||
### ❌ Don't: Use GeneralUtility::makeInstance() for Services
|
||||
```php
|
||||
// Old way (deprecated)
|
||||
$repository = GeneralUtility::makeInstance(ProductRepository::class);
|
||||
```
|
||||
|
||||
### ✅ Do: Use Dependency Injection
|
||||
```php
|
||||
// Modern way
|
||||
public function __construct(
|
||||
private readonly ProductRepository $repository
|
||||
) {}
|
||||
```
|
||||
|
||||
### ❌ Don't: Access $GLOBALS directly
|
||||
```php
|
||||
// Avoid global state
|
||||
$user = $GLOBALS['BE_USER'];
|
||||
$tsfe = $GLOBALS['TSFE'];
|
||||
```
|
||||
|
||||
### ✅ Do: Inject Context and Services
|
||||
```php
|
||||
public function __construct(
|
||||
private readonly Context $context,
|
||||
private readonly TypoScriptService $typoScriptService
|
||||
) {}
|
||||
```
|
||||
|
||||
### ❌ Don't: Use ext_tables.php for configuration
|
||||
```php
|
||||
// ext_tables.php (deprecated for most uses)
|
||||
```
|
||||
|
||||
### ✅ Do: Use dedicated configuration files
|
||||
```php
|
||||
// Configuration/Backend/Modules.php
|
||||
// Configuration/TCA/
|
||||
// Configuration/Services.yaml
|
||||
```
|
||||
|
||||
## Conformance Checklist
|
||||
|
||||
- [ ] Complete directory structure following best practices
|
||||
- [ ] composer.json with proper PSR-4 autoloading
|
||||
- [ ] Quality tools configured (php-cs-fixer, phpstan)
|
||||
- [ ] CI/CD pipeline (GitHub Actions or GitLab CI)
|
||||
- [ ] Comprehensive test coverage (unit, functional, acceptance)
|
||||
- [ ] Complete documentation in RST format
|
||||
- [ ] Service configuration in Services.yaml
|
||||
- [ ] Backend modules in Configuration/Backend/
|
||||
- [ ] TCA files in Configuration/TCA/
|
||||
- [ ] Language files in XLIFF format
|
||||
- [ ] Dependency injection throughout
|
||||
- [ ] No global state access
|
||||
- [ ] Security best practices followed
|
||||
- [ ] .editorconfig for consistent formatting
|
||||
- [ ] README.md with clear instructions
|
||||
- [ ] LICENSE file present
|
||||
610
skills/typo3-conformance/references/coding-guidelines.md
Normal file
610
skills/typo3-conformance/references/coding-guidelines.md
Normal file
@@ -0,0 +1,610 @@
|
||||
# TYPO3 Coding Guidelines
|
||||
|
||||
**Source:** TYPO3 Core API Reference - Coding Guidelines
|
||||
**Purpose:** PHP code style, formatting standards, and PSR-12 compliance for TYPO3 extensions
|
||||
|
||||
## PSR-12 Compliance
|
||||
|
||||
TYPO3 follows **PSR-12: Extended Coding Style** as the foundation for PHP code style.
|
||||
|
||||
**Key PSR-12 Requirements:**
|
||||
- 4 spaces for indentation (NO tabs)
|
||||
- Unix line endings (LF)
|
||||
- Maximum line length: 120 characters (soft limit), 80 recommended
|
||||
- Opening braces for classes/methods on same line
|
||||
- One statement per line
|
||||
- Visibility MUST be declared on all properties and methods
|
||||
|
||||
## Identifier Naming Conventions
|
||||
|
||||
### Variables and Methods: camelCase
|
||||
|
||||
```php
|
||||
// ✅ Right
|
||||
$userName = 'John';
|
||||
$totalPrice = 100;
|
||||
public function calculateTotal() {}
|
||||
public function getUserData() {}
|
||||
|
||||
// ❌ Wrong
|
||||
$user_name = 'John'; // snake_case
|
||||
$UserName = 'John'; // PascalCase
|
||||
public function CalculateTotal() {} // PascalCase
|
||||
public function get_user_data() {} // snake_case
|
||||
```
|
||||
|
||||
### Classes: UpperCamelCase (PascalCase)
|
||||
|
||||
```php
|
||||
// ✅ Right
|
||||
class UserController {}
|
||||
class PaymentService {}
|
||||
class ProductRepository {}
|
||||
|
||||
// ❌ Wrong
|
||||
class userController {} // camelCase
|
||||
class payment_service {} // snake_case
|
||||
class productRepository {} // camelCase
|
||||
```
|
||||
|
||||
### Constants: SCREAMING_SNAKE_CASE
|
||||
|
||||
```php
|
||||
// ✅ Right
|
||||
const MAX_UPLOAD_SIZE = 1024;
|
||||
const API_ENDPOINT = 'https://api.example.com';
|
||||
private const DEFAULT_TIMEOUT = 30;
|
||||
|
||||
// ❌ Wrong
|
||||
const maxUploadSize = 1024; // camelCase
|
||||
const ApiEndpoint = '...'; // PascalCase
|
||||
```
|
||||
|
||||
### Namespaces: UpperCamelCase
|
||||
|
||||
```php
|
||||
// ✅ Right
|
||||
namespace Vendor\ExtensionKey\Domain\Model;
|
||||
namespace Vendor\ExtensionKey\Controller;
|
||||
|
||||
// ❌ Wrong
|
||||
namespace vendor\extension_key\domain\model;
|
||||
namespace Vendor\extension_key\Controller;
|
||||
```
|
||||
|
||||
## Function and Method Naming
|
||||
|
||||
### Descriptive Names with Verbs
|
||||
|
||||
```php
|
||||
// ✅ Right: Verb + noun, descriptive
|
||||
public function getUserById(int $id): ?User {}
|
||||
public function calculateTotalPrice(array $items): float {}
|
||||
public function isValidEmail(string $email): bool {}
|
||||
public function hasPermission(string $action): bool {}
|
||||
|
||||
// ❌ Wrong: No verb, ambiguous
|
||||
public function user(int $id) {}
|
||||
public function price(array $items) {}
|
||||
public function email(string $email) {}
|
||||
public function permission(string $action) {}
|
||||
```
|
||||
|
||||
### Boolean Methods: is/has/can/should
|
||||
|
||||
```php
|
||||
// ✅ Right
|
||||
public function isActive(): bool {}
|
||||
public function hasAccess(): bool {}
|
||||
public function canEdit(): bool {}
|
||||
public function shouldRender(): bool {}
|
||||
|
||||
// ❌ Wrong
|
||||
public function active(): bool {}
|
||||
public function access(): bool {}
|
||||
public function checkEdit(): bool {}
|
||||
```
|
||||
|
||||
## Array Formatting
|
||||
|
||||
### Short Syntax Only
|
||||
|
||||
```php
|
||||
// ✅ Right: Short array syntax
|
||||
$items = [];
|
||||
$config = ['foo' => 'bar'];
|
||||
$users = [
|
||||
['name' => 'John', 'age' => 30],
|
||||
['name' => 'Jane', 'age' => 25],
|
||||
];
|
||||
|
||||
// ❌ Wrong: Long array syntax (deprecated)
|
||||
$items = array();
|
||||
$config = array('foo' => 'bar');
|
||||
```
|
||||
|
||||
### Multi-line Array Formatting
|
||||
|
||||
```php
|
||||
// ✅ Right: Proper indentation and trailing comma
|
||||
$configuration = [
|
||||
'key1' => 'value1',
|
||||
'key2' => 'value2',
|
||||
'nested' => [
|
||||
'subkey1' => 'subvalue1',
|
||||
'subkey2' => 'subvalue2',
|
||||
], // Trailing comma
|
||||
];
|
||||
|
||||
// ❌ Wrong: No trailing comma, inconsistent indentation
|
||||
$configuration = [
|
||||
'key1' => 'value1',
|
||||
'key2' => 'value2',
|
||||
'nested' => [
|
||||
'subkey1' => 'subvalue1',
|
||||
'subkey2' => 'subvalue2'
|
||||
]
|
||||
];
|
||||
```
|
||||
|
||||
## Conditional Statement Layout
|
||||
|
||||
### If/ElseIf/Else
|
||||
|
||||
```php
|
||||
// ✅ Right: Proper spacing and braces
|
||||
if ($condition) {
|
||||
doSomething();
|
||||
} elseif ($otherCondition) {
|
||||
doSomethingElse();
|
||||
} else {
|
||||
doDefault();
|
||||
}
|
||||
|
||||
// ❌ Wrong: Missing spaces, wrong brace placement
|
||||
if($condition){
|
||||
doSomething();
|
||||
}
|
||||
else if ($otherCondition) {
|
||||
doSomethingElse();
|
||||
}
|
||||
else {
|
||||
doDefault();
|
||||
}
|
||||
```
|
||||
|
||||
### Switch Statements
|
||||
|
||||
```php
|
||||
// ✅ Right
|
||||
switch ($status) {
|
||||
case 'active':
|
||||
processActive();
|
||||
break;
|
||||
case 'pending':
|
||||
processPending();
|
||||
break;
|
||||
default:
|
||||
processDefault();
|
||||
}
|
||||
|
||||
// ❌ Wrong: Inconsistent indentation
|
||||
switch ($status) {
|
||||
case 'active':
|
||||
processActive();
|
||||
break;
|
||||
case 'pending':
|
||||
processPending();
|
||||
break;
|
||||
default:
|
||||
processDefault();
|
||||
}
|
||||
```
|
||||
|
||||
## String Handling
|
||||
|
||||
### Single Quotes Default
|
||||
|
||||
```php
|
||||
// ✅ Right: Single quotes for simple strings
|
||||
$message = 'Hello, World!';
|
||||
$path = 'path/to/file.php';
|
||||
|
||||
// ❌ Wrong: Unnecessary double quotes
|
||||
$message = "Hello, World!"; // No variable interpolation
|
||||
$path = "path/to/file.php";
|
||||
```
|
||||
|
||||
### Double Quotes for Interpolation
|
||||
|
||||
```php
|
||||
// ✅ Right: Double quotes when interpolating
|
||||
$name = 'John';
|
||||
$message = "Hello, {$name}!";
|
||||
|
||||
// ❌ Wrong: Concatenation instead of interpolation
|
||||
$message = 'Hello, ' . $name . '!'; // Less readable
|
||||
```
|
||||
|
||||
### String Concatenation
|
||||
|
||||
```php
|
||||
// ✅ Right: Spaces around concatenation operator
|
||||
$fullPath = $basePath . '/' . $filename;
|
||||
$message = 'Hello ' . $name . ', welcome!';
|
||||
|
||||
// ❌ Wrong: No spaces around operator
|
||||
$fullPath = $basePath.'/'.$filename;
|
||||
$message = 'Hello '.$name.', welcome!';
|
||||
```
|
||||
|
||||
## PHPDoc Comment Standards
|
||||
|
||||
### Class Documentation
|
||||
|
||||
```php
|
||||
// ✅ Right: Complete class documentation
|
||||
/**
|
||||
* Service for calculating product prices with tax and discounts
|
||||
*
|
||||
* This service handles complex price calculations including:
|
||||
* - Tax rates based on country
|
||||
* - Quantity discounts
|
||||
* - Promotional codes
|
||||
*
|
||||
* @author John Doe <john@example.com>
|
||||
* @license GPL-2.0-or-later
|
||||
*/
|
||||
final class PriceCalculationService
|
||||
{
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Method Documentation
|
||||
|
||||
```php
|
||||
// ✅ Right: Complete method documentation
|
||||
/**
|
||||
* Calculate total price with tax for given items
|
||||
*
|
||||
* @param array<int, array{product: Product, quantity: int}> $items
|
||||
* @param string $countryCode ISO 3166-1 alpha-2 country code
|
||||
* @param float $discountPercent Discount percentage (0-100)
|
||||
* @return float Total price including tax
|
||||
* @throws \InvalidArgumentException If country code is invalid
|
||||
*/
|
||||
public function calculateTotal(
|
||||
array $items,
|
||||
string $countryCode,
|
||||
float $discountPercent = 0.0
|
||||
): float {
|
||||
// ...
|
||||
}
|
||||
|
||||
// ❌ Wrong: Missing or incomplete documentation
|
||||
/**
|
||||
* Calculates total
|
||||
*/
|
||||
public function calculateTotal($items, $countryCode, $discountPercent = 0.0) {
|
||||
// Missing param types, descriptions, return type
|
||||
}
|
||||
```
|
||||
|
||||
### Property Documentation
|
||||
|
||||
```php
|
||||
// ✅ Right
|
||||
/**
|
||||
* @var UserRepository User data repository
|
||||
*/
|
||||
private readonly UserRepository $userRepository;
|
||||
|
||||
/**
|
||||
* @var array<string, mixed> Configuration options
|
||||
*/
|
||||
private array $config = [];
|
||||
|
||||
// ❌ Wrong: No type hint or description
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $userRepository;
|
||||
```
|
||||
|
||||
## Curly Brace Placement
|
||||
|
||||
### Classes and Methods: Same Line
|
||||
|
||||
```php
|
||||
// ✅ Right: Opening brace on same line
|
||||
class MyController
|
||||
{
|
||||
public function indexAction(): ResponseInterface
|
||||
{
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ Wrong: Opening brace on new line (K&R style)
|
||||
class MyController {
|
||||
public function indexAction(): ResponseInterface {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Control Structures: Same Line
|
||||
|
||||
```php
|
||||
// ✅ Right
|
||||
if ($condition) {
|
||||
doSomething();
|
||||
}
|
||||
|
||||
foreach ($items as $item) {
|
||||
processItem($item);
|
||||
}
|
||||
|
||||
// ❌ Wrong: Opening brace on new line
|
||||
if ($condition)
|
||||
{
|
||||
doSomething();
|
||||
}
|
||||
```
|
||||
|
||||
## Namespace and Use Statements
|
||||
|
||||
### Namespace Structure
|
||||
|
||||
```php
|
||||
// ✅ Right: Proper namespace declaration
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Vendor\ExtensionKey\Domain\Model;
|
||||
|
||||
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
|
||||
|
||||
class Product extends AbstractEntity
|
||||
{
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Use Statements Organization
|
||||
|
||||
```php
|
||||
// ✅ Right: Grouped and sorted
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Vendor\ExtensionKey\Controller;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
|
||||
use Vendor\ExtensionKey\Domain\Repository\ProductRepository;
|
||||
|
||||
// ❌ Wrong: Unsorted, mixed
|
||||
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
|
||||
use Vendor\ExtensionKey\Domain\Repository\ProductRepository;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
```
|
||||
|
||||
## Type Declarations
|
||||
|
||||
### Strict Types
|
||||
|
||||
```php
|
||||
// ✅ Right: declare(strict_types=1) at the top
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Vendor\ExtensionKey\Service;
|
||||
|
||||
class MyService
|
||||
{
|
||||
public function calculate(int $value): float
|
||||
{
|
||||
return $value * 1.19;
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ Wrong: No strict types declaration
|
||||
<?php
|
||||
namespace Vendor\ExtensionKey\Service;
|
||||
|
||||
class MyService
|
||||
{
|
||||
public function calculate($value) // No type hints
|
||||
{
|
||||
return $value * 1.19;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Property Type Declarations (PHP 7.4+)
|
||||
|
||||
```php
|
||||
// ✅ Right: Typed properties
|
||||
class User
|
||||
{
|
||||
private string $username;
|
||||
private int $id;
|
||||
private ?string $email = null;
|
||||
private array $roles = [];
|
||||
}
|
||||
|
||||
// ❌ Wrong: No type declarations
|
||||
class User
|
||||
{
|
||||
private $username;
|
||||
private $id;
|
||||
private $email;
|
||||
private $roles;
|
||||
}
|
||||
```
|
||||
|
||||
## File Structure
|
||||
|
||||
### Standard File Template
|
||||
|
||||
```php
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace Vendor\ExtensionKey\Domain\Model;
|
||||
|
||||
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
|
||||
|
||||
/**
|
||||
* Product model
|
||||
*/
|
||||
class Product extends AbstractEntity
|
||||
{
|
||||
/**
|
||||
* @var string Product title
|
||||
*/
|
||||
private string $title = '';
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function setTitle(string $title): void
|
||||
{
|
||||
$this->title = $title;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## PHPStan and Static Analysis
|
||||
|
||||
TYPO3 extensions should use **PHPStan level 10** (strictest) for maximum type safety and code quality.
|
||||
|
||||
### PHPStan Baseline Hygiene
|
||||
|
||||
**Critical Rule:** New code must NEVER add errors to `phpstan-baseline.neon`.
|
||||
|
||||
The baseline file exists only for legacy code that hasn't been refactored yet. All new code must pass PHPStan level 10 without baseline suppression.
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
# Check if your changes added to baseline
|
||||
git diff HEAD~1 Build/phpstan-baseline.neon
|
||||
|
||||
# If count increased, you MUST fix the underlying issues
|
||||
# Example: count: 8 → count: 9 means you added 1 new error
|
||||
```
|
||||
|
||||
### Type-Safe Mixed Value Handling
|
||||
|
||||
**Common PHPStan Error:** "Cannot cast mixed to int/string/bool"
|
||||
|
||||
**Occurs with:** TypoScript configuration, user input, API responses
|
||||
|
||||
**❌ Wrong (adds to baseline):**
|
||||
```php
|
||||
// PHPStan: Cannot cast mixed to int
|
||||
$maxSize = (int) ($conf['maxSize'] ?? 0);
|
||||
```
|
||||
|
||||
**✅ Right (passes level 10):**
|
||||
```php
|
||||
// Type-guard before casting
|
||||
$value = $conf['maxSize'] ?? 0;
|
||||
if (is_numeric($value)) {
|
||||
$maxSize = (int) $value;
|
||||
} else {
|
||||
$maxSize = 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Common Mixed Type Patterns
|
||||
|
||||
**Arrays from configuration:**
|
||||
```php
|
||||
// ❌ Wrong
|
||||
$items = (array) $conf['items'];
|
||||
|
||||
// ✅ Right
|
||||
$items = [];
|
||||
if (isset($conf['items']) && is_array($conf['items'])) {
|
||||
$items = $conf['items'];
|
||||
}
|
||||
```
|
||||
|
||||
**Strings from user input:**
|
||||
```php
|
||||
// ❌ Wrong
|
||||
$name = (string) $_POST['name'];
|
||||
|
||||
// ✅ Right
|
||||
$name = '';
|
||||
if (isset($_POST['name']) && is_string($_POST['name'])) {
|
||||
$name = $_POST['name'];
|
||||
}
|
||||
```
|
||||
|
||||
**Boolean from configuration:**
|
||||
```php
|
||||
// ❌ Wrong
|
||||
$enabled = (bool) $conf['enabled'];
|
||||
|
||||
// ✅ Right
|
||||
$enabled = isset($conf['enabled']) && (bool) $conf['enabled'];
|
||||
```
|
||||
|
||||
### Pre-Commit PHPStan Check
|
||||
|
||||
Always run PHPStan before committing:
|
||||
|
||||
```bash
|
||||
# Run PHPStan
|
||||
composer ci:php:stan
|
||||
|
||||
# Verify no new baseline entries
|
||||
git diff Build/phpstan-baseline.neon
|
||||
|
||||
# If baseline changed, fix the issues instead of committing the baseline
|
||||
```
|
||||
|
||||
## Conformance Checklist
|
||||
|
||||
- [ ] All PHP files use 4 spaces for indentation (NO tabs)
|
||||
- [ ] Variables and methods use camelCase
|
||||
- [ ] Classes use UpperCamelCase
|
||||
- [ ] Constants use SCREAMING_SNAKE_CASE
|
||||
- [ ] Array short syntax [] used (not array())
|
||||
- [ ] Multi-line arrays have trailing commas
|
||||
- [ ] Strings use single quotes by default
|
||||
- [ ] String concatenation has spaces around `.` operator
|
||||
- [ ] All classes have PHPDoc comments
|
||||
- [ ] All public methods have PHPDoc with @param and @return
|
||||
- [ ] Opening braces on same line for classes/methods
|
||||
- [ ] declare(strict_types=1) at top of all PHP files
|
||||
- [ ] Proper namespace structure matching directory
|
||||
- [ ] Use statements grouped and sorted
|
||||
- [ ] Type declarations on all properties and method parameters
|
||||
- [ ] Maximum line length 120 characters
|
||||
- [ ] Unix line endings (LF)
|
||||
- [ ] PHPStan level 10 passes with zero errors
|
||||
- [ ] No new errors added to phpstan-baseline.neon
|
||||
- [ ] Type-guards before casting mixed values (is_numeric, is_string, is_array)
|
||||
468
skills/typo3-conformance/references/composer-validation.md
Normal file
468
skills/typo3-conformance/references/composer-validation.md
Normal file
@@ -0,0 +1,468 @@
|
||||
# Composer.json Validation Standards (TYPO3 v13)
|
||||
|
||||
**Source:** TYPO3 Core API Reference v13.4 - FileStructure/ComposerJson.html
|
||||
**Purpose:** Complete validation rules for composer.json in TYPO3 extensions
|
||||
|
||||
## Mandatory Fields
|
||||
|
||||
### name
|
||||
**Format:** `<vendor>/<dashed-extension-key>`
|
||||
|
||||
**Examples:**
|
||||
```json
|
||||
"name": "vendor-name/my-extension"
|
||||
"name": "johndoe/some-extension"
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
jq -r '.name' composer.json | grep -E '^[a-z0-9-]+/[a-z0-9-]+$' && echo "✅ Valid" || echo "❌ Invalid format"
|
||||
```
|
||||
|
||||
### type
|
||||
**Required Value:** `typo3-cms-extension` (for third-party extensions)
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
jq -r '.type' composer.json | grep -q "typo3-cms-extension" && echo "✅ Correct type" || echo "❌ Wrong type"
|
||||
```
|
||||
|
||||
### description
|
||||
**Format:** Single-line summary describing what the extension does
|
||||
|
||||
**Requirements:**
|
||||
- Clear, concise description of extension functionality
|
||||
- Should identify the vendor/company for professional extensions
|
||||
- Avoid vague descriptions like "An extension" or "Utility tools"
|
||||
|
||||
**Good Examples:**
|
||||
```json
|
||||
"description": "Adds image support to CKEditor5 RTE - by Netresearch"
|
||||
"description": "TYPO3 extension for advanced content management by Vendor GmbH"
|
||||
"description": "Provides custom form elements for newsletter subscription"
|
||||
```
|
||||
|
||||
**Bad Examples:**
|
||||
```json
|
||||
"description": "Extension" // Too vague
|
||||
"description": "Some tools" // Meaningless
|
||||
"description": "" // Empty
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
# Check description exists and is not empty
|
||||
jq -r '.description' composer.json | grep -q . && echo "✅ Has description" || echo "❌ Missing description"
|
||||
|
||||
# Check description length (should be meaningful, >20 chars)
|
||||
DESC_LEN=$(jq -r '.description | length' composer.json)
|
||||
[[ $DESC_LEN -gt 20 ]] && echo "✅ Description is meaningful" || echo "⚠️ Description too short"
|
||||
```
|
||||
|
||||
### license
|
||||
**Recommended:** `GPL-2.0-only` or `GPL-2.0-or-later`
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
jq -r '.license' composer.json | grep -qE "GPL-2.0-(only|or-later)" && echo "✅ GPL license" || echo "⚠️ Check license"
|
||||
```
|
||||
|
||||
### require
|
||||
**Minimum:** Must specify `typo3/cms-core` with version constraints
|
||||
|
||||
**Version Constraint Format:**
|
||||
- `^12.4 || ^13.4` - Multiple major versions (recommended for v12/v13 compat)
|
||||
- `^12.4` - Single major version
|
||||
- `>=12.4` ❌ - NO upper bound (not recommended)
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
# Check typo3/cms-core present
|
||||
jq -r '.require["typo3/cms-core"]' composer.json | grep -q . && echo "✅ TYPO3 core required" || echo "❌ Missing typo3/cms-core"
|
||||
|
||||
# Check for upper bound (^ or specific upper version)
|
||||
jq -r '.require["typo3/cms-core"]' composer.json | grep -qE '(\^|[0-9]+\.[0-9]+\.[0-9]+-[0-9]+\.[0-9]+\.[0-9]+)' && echo "✅ Has upper bound" || echo "⚠️ Missing upper bound"
|
||||
```
|
||||
|
||||
### autoload
|
||||
**Format:** PSR-4 mapping to Classes/ directory
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Vendor\\ExtensionName\\": "Classes/"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
jq -r '.autoload["psr-4"]' composer.json | grep -q "Classes" && echo "✅ PSR-4 autoload configured" || echo "❌ Missing autoload"
|
||||
```
|
||||
|
||||
### extra.typo3/cms.extension-key
|
||||
**Required:** Maps to underscored extension key
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
"extra": {
|
||||
"typo3/cms": {
|
||||
"extension-key": "my_extension"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
jq -r '.extra."typo3/cms"."extension-key"' composer.json | grep -q . && echo "✅ Extension key defined" || echo "❌ Missing extension-key"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Recommended Fields (Professional Extensions)
|
||||
|
||||
### authors
|
||||
**Format:** Array of author objects with name, email, role, homepage
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
"authors": [
|
||||
{
|
||||
"name": "Developer Name",
|
||||
"email": "developer@company.com",
|
||||
"role": "Developer",
|
||||
"homepage": "https://www.company.com/"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Required Sub-Fields:**
|
||||
| Field | Format | Purpose |
|
||||
|-------|--------|---------|
|
||||
| `name` | String | Developer's full name |
|
||||
| `email` | Email address | Contact email |
|
||||
| `role` | String | `Developer`, `Maintainer`, `Lead Developer` |
|
||||
| `homepage` | URL | Company or personal website |
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
# Check authors array exists
|
||||
jq -r '.authors' composer.json | grep -q "name" && echo "✅ Has authors" || echo "⚠️ Missing authors"
|
||||
|
||||
# Check authors have email
|
||||
jq -r '.authors[].email' composer.json | grep -q "@" && echo "✅ Has author emails" || echo "⚠️ Missing author emails"
|
||||
|
||||
# Check authors have homepage
|
||||
jq -r '.authors[].homepage' composer.json | grep -q "http" && echo "✅ Has author homepage" || echo "⚠️ Missing author homepage"
|
||||
```
|
||||
|
||||
### homepage
|
||||
**Format:** URL to project repository or documentation
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
"homepage": "https://github.com/vendor/extension-name"
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
jq -r '.homepage' composer.json | grep -qE "^https?://" && echo "✅ Has homepage" || echo "⚠️ Missing homepage"
|
||||
```
|
||||
|
||||
### support
|
||||
**Format:** Object with support channels
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
"support": {
|
||||
"issues": "https://github.com/vendor/extension/issues",
|
||||
"source": "https://github.com/vendor/extension"
|
||||
}
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
jq -r '.support.issues' composer.json | grep -q "http" && echo "✅ Has issues URL" || echo "⚠️ Missing issues URL"
|
||||
```
|
||||
|
||||
### keywords
|
||||
**Format:** Array of relevant keywords for discoverability
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
"keywords": [
|
||||
"TYPO3",
|
||||
"extension",
|
||||
"content",
|
||||
"management"
|
||||
]
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
jq -r '.keywords | length' composer.json | grep -qE '^[1-9]' && echo "✅ Has keywords" || echo "⚠️ Missing keywords"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Complete Required Fields Checklist
|
||||
|
||||
**Mandatory (MUST have):**
|
||||
- [ ] `name` - vendor/package format
|
||||
- [ ] `type` - must be `typo3-cms-extension`
|
||||
- [ ] `description` - clear, concise description
|
||||
- [ ] `license` - SPDX identifier (GPL-2.0-or-later, AGPL-3.0-or-later)
|
||||
- [ ] `require.typo3/cms-core` - with upper bound constraint
|
||||
- [ ] `require.php` - PHP version constraint
|
||||
- [ ] `autoload.psr-4` - mapping to Classes/
|
||||
- [ ] `extra.typo3/cms.extension-key` - underscored extension key
|
||||
|
||||
**Recommended (SHOULD have):**
|
||||
- [ ] `authors` - with name, email, role, homepage
|
||||
- [ ] `homepage` - project repository URL
|
||||
- [ ] `support.issues` - issue tracker URL
|
||||
- [ ] `keywords` - for discoverability
|
||||
|
||||
---
|
||||
|
||||
## Deprecated Properties
|
||||
|
||||
### replace with typo3-ter vendor
|
||||
**Status:** DEPRECATED - Legacy TER integration approach
|
||||
|
||||
**Detection:**
|
||||
```bash
|
||||
jq -r '.replace' composer.json | grep -q "typo3-ter" && echo "⚠️ Deprecated: typo3-ter in replace" || echo "✅ No deprecated replace"
|
||||
```
|
||||
|
||||
### replace with "ext_key": "self.version"
|
||||
**Status:** DEPRECATED - Legacy dependency specification
|
||||
|
||||
**Detection:**
|
||||
```bash
|
||||
jq -r '.replace' composer.json | grep -qE '"[a-z_]+": "self.version"' && echo "⚠️ Deprecated: self.version replace" || echo "✅ No self.version"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## TYPO3 v12-v13 Version Constraints
|
||||
|
||||
### Recommended Format
|
||||
```json
|
||||
"require": {
|
||||
"typo3/cms-core": "^12.4 || ^13.4",
|
||||
"php": "^8.1"
|
||||
}
|
||||
```
|
||||
|
||||
### PHP Version Constraints
|
||||
```json
|
||||
"require": {
|
||||
"php": "^8.1" // TYPO3 v12: PHP 8.1-8.4
|
||||
}
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
# Check PHP constraint
|
||||
jq -r '.require.php' composer.json | grep -qE '\^8\.[1-4]' && echo "✅ Valid PHP constraint" || echo "⚠️ Check PHP version"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Synchronization with ext_emconf.php
|
||||
|
||||
**Critical:** `composer.json` and `ext_emconf.php` must have matching dependency constraints.
|
||||
|
||||
**Mapping:**
|
||||
|
||||
| composer.json | ext_emconf.php |
|
||||
|--------------|----------------|
|
||||
| `require.typo3/cms-core` | `constraints.depends.typo3` |
|
||||
| `require.php` | `constraints.depends.php` |
|
||||
| `require.*` | `constraints.depends.*` |
|
||||
|
||||
**Example Synchronization:**
|
||||
|
||||
composer.json:
|
||||
```json
|
||||
"require": {
|
||||
"typo3/cms-core": "^12.4 || ^13.4",
|
||||
"php": "^8.1",
|
||||
"typo3/cms-fluid": "^12.4 || ^13.4"
|
||||
}
|
||||
```
|
||||
|
||||
ext_emconf.php:
|
||||
```php
|
||||
'constraints' => [
|
||||
'depends' => [
|
||||
'typo3' => '12.4.0-13.4.99',
|
||||
'php' => '8.1.0-8.4.99',
|
||||
'fluid' => '12.4.0-13.4.99',
|
||||
],
|
||||
],
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Complete Validation Script
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# validate-composer.sh
|
||||
|
||||
ERRORS=0
|
||||
|
||||
echo "=== Composer.json Validation ===="
|
||||
|
||||
# Check mandatory fields
|
||||
jq -r '.name' composer.json > /dev/null 2>&1 || { echo "❌ Missing 'name'"; ((ERRORS++)); }
|
||||
jq -r '.type' composer.json | grep -q "typo3-cms-extension" || { echo "❌ Wrong or missing 'type'"; ((ERRORS++)); }
|
||||
jq -r '.description' composer.json | grep -q . || { echo "❌ Missing 'description'"; ((ERRORS++)); }
|
||||
|
||||
# Check description is meaningful (>20 chars)
|
||||
DESC_LEN=$(jq -r '.description | length' composer.json 2>/dev/null)
|
||||
[[ $DESC_LEN -lt 20 ]] && { echo "⚠️ Description too short (should be >20 chars)"; ((WARNINGS++)); }
|
||||
|
||||
# Check typo3/cms-core
|
||||
jq -r '.require["typo3/cms-core"]' composer.json | grep -q . || { echo "❌ Missing typo3/cms-core"; ((ERRORS++)); }
|
||||
|
||||
# Check version constraints have upper bounds
|
||||
jq -r '.require["typo3/cms-core"]' composer.json | grep -qE '(\^|[0-9]+\.[0-9]+\.[0-9]+-[0-9]+\.[0-9]+\.[0-9]+)' || { echo "⚠️ TYPO3 constraint missing upper bound"; ((ERRORS++)); }
|
||||
|
||||
# Check autoload
|
||||
jq -r '.autoload["psr-4"]' composer.json | grep -q "Classes" || { echo "❌ Missing PSR-4 autoload"; ((ERRORS++)); }
|
||||
|
||||
# Check extension-key
|
||||
jq -r '.extra."typo3/cms"."extension-key"' composer.json | grep -q . || { echo "❌ Missing extension-key"; ((ERRORS++)); }
|
||||
|
||||
# Check for deprecated replace
|
||||
jq -r '.replace' composer.json 2>/dev/null | grep -q "typo3-ter\|self.version" && echo "⚠️ Deprecated replace property found"
|
||||
|
||||
echo ""
|
||||
echo "Validation complete: $ERRORS critical errors"
|
||||
exit $ERRORS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Optional but Recommended Fields
|
||||
|
||||
### require-dev
|
||||
**Purpose:** Development dependencies not needed in production
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
"require-dev": {
|
||||
"typo3/coding-standards": "^0.7",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpunit/phpunit": "^10.0"
|
||||
}
|
||||
```
|
||||
|
||||
### suggest
|
||||
**Purpose:** Optional packages that enhance functionality
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
"suggest": {
|
||||
"typo3/cms-filelist": "For file browser functionality",
|
||||
"typo3/cms-reactions": "For webhook support"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Packagist Publication:** Publishing to Packagist makes extensions available in TYPO3 Extension Repository automatically
|
||||
2. **Documentation Rendering:** `composer.json` is **REQUIRED** for extensions with documentation on docs.typo3.org
|
||||
3. **Version Constraint Strategy:**
|
||||
- Use `^` for flexible upper bounds
|
||||
- Specify both major version ranges for v12/v13 compatibility
|
||||
- Always include upper bounds (avoid `>=` without upper limit)
|
||||
4. **Namespace Alignment:** PSR-4 namespace should match vendor/extension structure
|
||||
5. **Composer Priority:** Composer-based installations prioritize `composer.json` over `ext_emconf.php` for dependency resolution
|
||||
|
||||
---
|
||||
|
||||
## Common Violations and Fixes
|
||||
|
||||
### Missing extra.typo3/cms.extension-key
|
||||
|
||||
❌ **Before:**
|
||||
```json
|
||||
{
|
||||
"name": "vendor/my-extension",
|
||||
"type": "typo3-cms-extension"
|
||||
}
|
||||
```
|
||||
|
||||
✅ **After:**
|
||||
```json
|
||||
{
|
||||
"name": "vendor/my-extension",
|
||||
"type": "typo3-cms-extension",
|
||||
"extra": {
|
||||
"typo3/cms": {
|
||||
"extension-key": "my_extension"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Version Constraint Without Upper Bound
|
||||
|
||||
❌ **Before:**
|
||||
```json
|
||||
"require": {
|
||||
"typo3/cms-core": ">=12.4"
|
||||
}
|
||||
```
|
||||
|
||||
✅ **After:**
|
||||
```json
|
||||
"require": {
|
||||
"typo3/cms-core": "^12.4 || ^13.4"
|
||||
}
|
||||
```
|
||||
|
||||
### Deprecated replace Property
|
||||
|
||||
❌ **Before:**
|
||||
```json
|
||||
"replace": {
|
||||
"typo3-ter/my-extension": "self.version"
|
||||
}
|
||||
```
|
||||
|
||||
✅ **After:**
|
||||
```json
|
||||
// Remove replace property entirely
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Additional Validation Commands
|
||||
|
||||
### Check all required dependencies have upper bounds
|
||||
```bash
|
||||
jq -r '.require | to_entries[] | select(.value | test(">=") and (test("\\^") | not)) | .key' composer.json
|
||||
```
|
||||
|
||||
### Verify package type
|
||||
```bash
|
||||
jq -r '.type' composer.json | grep -q "typo3-cms-extension" && echo "✅" || echo "❌ Wrong package type"
|
||||
```
|
||||
|
||||
### Check PSR-4 namespace format
|
||||
```bash
|
||||
jq -r '.autoload["psr-4"] | keys[]' composer.json | grep -E '^[A-Z][a-zA-Z0-9]*\\\\[A-Z][a-zA-Z0-9]*\\\\$' && echo "✅ Valid namespace" || echo "⚠️ Check namespace format"
|
||||
```
|
||||
|
||||
### Validate JSON syntax
|
||||
```bash
|
||||
jq . composer.json > /dev/null && echo "✅ Valid JSON" || echo "❌ JSON syntax error"
|
||||
```
|
||||
1344
skills/typo3-conformance/references/crowdin-integration.md
Normal file
1344
skills/typo3-conformance/references/crowdin-integration.md
Normal file
File diff suppressed because it is too large
Load Diff
524
skills/typo3-conformance/references/development-environment.md
Normal file
524
skills/typo3-conformance/references/development-environment.md
Normal file
@@ -0,0 +1,524 @@
|
||||
# Development Environment Standards
|
||||
|
||||
**Purpose:** Validate development environment setup for consistent, reproducible TYPO3 extension development
|
||||
|
||||
## Why Development Environment Matters
|
||||
|
||||
A properly configured development environment ensures:
|
||||
|
||||
- ✅ **Consistency** - All developers work with identical PHP/TYPO3/database versions
|
||||
- ✅ **Onboarding** - New contributors can start immediately without complex setup
|
||||
- ✅ **CI/CD Parity** - Local environment matches production/staging
|
||||
- ✅ **Reproducibility** - Bugs are reproducible across all environments
|
||||
- ✅ **Cross-Platform** - Works on macOS, Linux, Windows (WSL)
|
||||
|
||||
Without standardized dev environment:
|
||||
- ❌ "Works on my machine" syndrome
|
||||
- ❌ Inconsistent PHP/database versions causing bugs
|
||||
- ❌ Complex setup discourages contributions
|
||||
- ❌ CI failures that don't reproduce locally
|
||||
|
||||
## TYPO3 Community Standards
|
||||
|
||||
### DDEV - Primary Recommendation
|
||||
|
||||
**DDEV** is the **de facto standard** for TYPO3 development:
|
||||
|
||||
- ✅ Official TYPO3 core development uses DDEV
|
||||
- ✅ TYPO3 Best Practices (Tea extension) uses DDEV
|
||||
- ✅ TYPO3 documentation recommends DDEV
|
||||
- ✅ Cross-platform support (Docker-based)
|
||||
- ✅ Preconfigured for TYPO3 (`ddev config --project-type=typo3`)
|
||||
|
||||
**Alternative:** Docker Compose (acceptable, more manual configuration)
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
### 1. DDEV Configuration
|
||||
|
||||
**Check for `.ddev/` directory:**
|
||||
|
||||
```bash
|
||||
ls -la .ddev/
|
||||
```
|
||||
|
||||
**Required files:**
|
||||
- `.ddev/config.yaml` - Core DDEV configuration
|
||||
- `.ddev/.gitignore` - Excludes dynamic files (import-db, .ddev-docker-compose-*.yaml)
|
||||
|
||||
**Optional but recommended:**
|
||||
- `.ddev/config.typo3.yaml` - TYPO3-specific settings
|
||||
- `.ddev/commands/` - Custom DDEV commands
|
||||
- `.ddev/docker-compose.*.yaml` - Additional services
|
||||
|
||||
**Severity if missing:** 🟡 **Medium** - Indicates no standardized dev environment
|
||||
|
||||
### 2. DDEV config.yaml Structure
|
||||
|
||||
**Minimum DDEV Configuration:**
|
||||
|
||||
```yaml
|
||||
name: extension-name
|
||||
type: typo3
|
||||
docroot: .Build/public
|
||||
php_version: "8.2" # Match composer.json minimum
|
||||
webserver_type: nginx-fpm
|
||||
router_http_port: "80"
|
||||
router_https_port: "443"
|
||||
xdebug_enabled: false
|
||||
additional_hostnames: []
|
||||
additional_fqdns: []
|
||||
database:
|
||||
type: mariadb
|
||||
version: "10.11"
|
||||
omit_containers: [ddev-ssh-agent]
|
||||
```
|
||||
|
||||
**Validation Rules:**
|
||||
|
||||
| Field | Validation | Example | Severity |
|
||||
|-------|-----------|---------|----------|
|
||||
| `name` | Should match extension key or composer name | `rte-ckeditor-image` | Low |
|
||||
| `type` | Must be `typo3` | `typo3` | High |
|
||||
| `docroot` | Should match composer.json web-dir | `.Build/public` | High |
|
||||
| `php_version` | Should match composer.json minimum PHP | `"8.2"` | High |
|
||||
| `database.type` | Should be `mariadb` (TYPO3 standard) | `mariadb` | Medium |
|
||||
| `database.version` | Should be LTS version (10.11 or 11.x) | `"10.11"` | Medium |
|
||||
|
||||
**Example Check:**
|
||||
|
||||
```bash
|
||||
# Extension composer.json
|
||||
"require": {
|
||||
"php": "^8.2 || ^8.3 || ^8.4",
|
||||
"typo3/cms-core": "^13.4"
|
||||
}
|
||||
"extra": {
|
||||
"typo3/cms": {
|
||||
"web-dir": ".Build/public"
|
||||
}
|
||||
}
|
||||
|
||||
# DDEV config.yaml SHOULD have:
|
||||
php_version: "8.2" # ✅ Matches minimum
|
||||
docroot: .Build/public # ✅ Matches web-dir
|
||||
type: typo3 # ✅ Correct type
|
||||
|
||||
# DDEV config.yaml SHOULD NOT have:
|
||||
php_version: "7.4" # ❌ Below minimum
|
||||
docroot: public # ❌ Doesn't match web-dir
|
||||
type: php # ❌ Wrong type
|
||||
```
|
||||
|
||||
### 3. Docker Compose (Alternative)
|
||||
|
||||
If DDEV not present, check for `docker-compose.yml`:
|
||||
|
||||
**Minimum Docker Compose Configuration:**
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
web:
|
||||
image: ghcr.io/typo3/core-testing-php82:latest
|
||||
volumes:
|
||||
- .:/var/www/html
|
||||
working_dir: /var/www/html
|
||||
ports:
|
||||
- "8000:80"
|
||||
environment:
|
||||
TYPO3_CONTEXT: Development
|
||||
|
||||
db:
|
||||
image: mariadb:10.11
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_DATABASE: typo3
|
||||
volumes:
|
||||
- db_data:/var/lib/mysql
|
||||
|
||||
volumes:
|
||||
db_data:
|
||||
```
|
||||
|
||||
**Validation Rules:**
|
||||
|
||||
| Service | Validation | Severity |
|
||||
|---------|-----------|----------|
|
||||
| `web` service exists | Required | High |
|
||||
| PHP version matches composer.json | Required | High |
|
||||
| `db` service exists | Required | Medium |
|
||||
| Database type is MariaDB/MySQL | Recommended | Low |
|
||||
| Volumes preserve database data | Required | High |
|
||||
|
||||
**Severity if missing:** 🟡 **Medium** - Harder to onboard, but not critical
|
||||
|
||||
### 4. DevContainer (VS Code Remote Containers)
|
||||
|
||||
Check for `.devcontainer/devcontainer.json`:
|
||||
|
||||
**Example DevContainer Configuration:**
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "TYPO3 Extension Development",
|
||||
"dockerComposeFile": ["../docker-compose.yml"],
|
||||
"service": "web",
|
||||
"workspaceFolder": "/var/www/html",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"bmewburn.vscode-intelephense-client",
|
||||
"xdebug.php-debug",
|
||||
"EditorConfig.EditorConfig"
|
||||
],
|
||||
"settings": {
|
||||
"php.validate.executablePath": "/usr/local/bin/php"
|
||||
}
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/common-utils:2": {},
|
||||
"ghcr.io/devcontainers/features/node:1": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
- File exists: ✅ Good (VS Code support)
|
||||
- References docker-compose.yml or DDEV: ✅ Integrated approach
|
||||
- Empty directory: ⚠️ Incomplete setup
|
||||
|
||||
**Severity if missing:** 🟢 **Low** - Nice to have, not required
|
||||
|
||||
## DDEV-Specific Best Practices
|
||||
|
||||
### TYPO3-Optimized Settings
|
||||
|
||||
**`.ddev/config.typo3.yaml`:**
|
||||
|
||||
```yaml
|
||||
# TYPO3-specific DDEV configuration
|
||||
override_config: false
|
||||
web_extra_daemons:
|
||||
- name: "typo3-backend-lock-handler"
|
||||
command: "/var/www/html/.Build/bin/typo3 scheduler:run"
|
||||
directory: /var/www/html
|
||||
|
||||
hooks:
|
||||
post-start:
|
||||
- exec: composer install
|
||||
- exec: .Build/bin/typo3 cache:flush
|
||||
|
||||
# Additional PHP settings for TYPO3
|
||||
php_ini:
|
||||
memory_limit: 512M
|
||||
max_execution_time: 240
|
||||
upload_max_filesize: 32M
|
||||
post_max_size: 32M
|
||||
```
|
||||
|
||||
### Custom DDEV Commands
|
||||
|
||||
**`.ddev/commands/web/typo3`:**
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
## Description: Run TYPO3 CLI commands
|
||||
## Usage: typo3 [args]
|
||||
## Example: "ddev typo3 cache:flush"
|
||||
|
||||
.Build/bin/typo3 "$@"
|
||||
```
|
||||
|
||||
**`.ddev/commands/web/test-unit`:**
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
## Description: Run unit tests
|
||||
## Usage: test-unit [args]
|
||||
|
||||
.Build/bin/phpunit -c Build/phpunit/UnitTests.xml "$@"
|
||||
```
|
||||
|
||||
**`.ddev/commands/web/test-functional`:**
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
## Description: Run functional tests
|
||||
## Usage: test-functional [args]
|
||||
|
||||
.Build/bin/phpunit -c Build/phpunit/FunctionalTests.xml "$@"
|
||||
```
|
||||
|
||||
## Conformance Evaluation Workflow
|
||||
|
||||
### Step 1: Detect Development Environment Type
|
||||
|
||||
```bash
|
||||
# Check for DDEV
|
||||
if [ -f ".ddev/config.yaml" ]; then
|
||||
DEV_ENV="ddev"
|
||||
SCORE=20 # Full points for DDEV
|
||||
|
||||
# Check for Docker Compose
|
||||
elif [ -f "docker-compose.yml" ]; then
|
||||
DEV_ENV="docker-compose"
|
||||
SCORE=15 # Good, but manual
|
||||
|
||||
# Check for DevContainer only
|
||||
elif [ -f ".devcontainer/devcontainer.json" ]; then
|
||||
DEV_ENV="devcontainer"
|
||||
SCORE=10 # VS Code specific
|
||||
|
||||
# No dev environment
|
||||
else
|
||||
DEV_ENV="none"
|
||||
SCORE=0
|
||||
fi
|
||||
```
|
||||
|
||||
### Step 2: Validate Configuration Against Extension
|
||||
|
||||
**For DDEV:**
|
||||
|
||||
```bash
|
||||
# Extract extension requirements
|
||||
MIN_PHP=$(jq -r '.require.php' composer.json | grep -oE '[0-9]+\.[0-9]+' | head -1)
|
||||
WEB_DIR=$(jq -r '.extra.typo3.cms."web-dir"' composer.json)
|
||||
|
||||
# Validate DDEV config
|
||||
DDEV_PHP=$(grep 'php_version:' .ddev/config.yaml | awk '{print $2}' | tr -d '"')
|
||||
DDEV_DOCROOT=$(grep 'docroot:' .ddev/config.yaml | awk '{print $2}')
|
||||
DDEV_TYPE=$(grep 'type:' .ddev/config.yaml | awk '{print $2}')
|
||||
|
||||
# Compare
|
||||
if [ "${DDEV_PHP}" != "${MIN_PHP}" ]; then
|
||||
echo "⚠️ PHP version mismatch: DDEV ${DDEV_PHP} vs required ${MIN_PHP}"
|
||||
fi
|
||||
|
||||
if [ "${DDEV_DOCROOT}" != "${WEB_DIR}" ]; then
|
||||
echo "⚠️ Docroot mismatch: DDEV ${DDEV_DOCROOT} vs composer ${WEB_DIR}"
|
||||
fi
|
||||
|
||||
if [ "${DDEV_TYPE}" != "typo3" ]; then
|
||||
echo "❌ DDEV type should be 'typo3', found '${DDEV_TYPE}'"
|
||||
fi
|
||||
```
|
||||
|
||||
### Step 3: Check for Recommended Enhancements
|
||||
|
||||
```bash
|
||||
# DDEV commands
|
||||
if [ -d ".ddev/commands/web" ]; then
|
||||
COMMANDS=$(ls .ddev/commands/web/ 2>/dev/null | wc -l)
|
||||
echo "✅ DDEV has ${COMMANDS} custom commands"
|
||||
else
|
||||
echo "ℹ️ No custom DDEV commands (consider adding typo3, test-unit, test-functional)"
|
||||
fi
|
||||
|
||||
# TYPO3-specific config
|
||||
if [ -f ".ddev/config.typo3.yaml" ]; then
|
||||
echo "✅ TYPO3-specific DDEV configuration present"
|
||||
else
|
||||
echo "ℹ️ No TYPO3-specific config (optional)"
|
||||
fi
|
||||
```
|
||||
|
||||
## Conformance Report Integration
|
||||
|
||||
### When Evaluating Development Environment:
|
||||
|
||||
**In "Best Practices" Section:**
|
||||
|
||||
```markdown
|
||||
### Development Environment
|
||||
|
||||
**Configuration:**
|
||||
|
||||
- ✅ DDEV configured (.ddev/config.yaml present)
|
||||
- ✅ PHP version matches composer.json minimum (8.2)
|
||||
- ✅ Docroot matches composer.json web-dir (.Build/public)
|
||||
- ✅ Type set to 'typo3' for TYPO3-optimized setup
|
||||
- ✅ MariaDB 10.11 (LTS) configured
|
||||
- ✅ Custom DDEV commands for testing (test-unit, test-functional)
|
||||
- ℹ️ Optional: TYPO3-specific config (.ddev/config.typo3.yaml) could enhance setup
|
||||
|
||||
**Or with issues:**
|
||||
|
||||
- ❌ No development environment configuration
|
||||
- Missing: .ddev/config.yaml, docker-compose.yml
|
||||
- Impact: Inconsistent development environments, difficult onboarding
|
||||
- Severity: Medium
|
||||
- Recommendation: Add DDEV configuration from Tea extension pattern
|
||||
- Reference: https://github.com/TYPO3BestPractices/tea/tree/main/.ddev
|
||||
|
||||
- ⚠️ DDEV PHP version mismatch
|
||||
- File: .ddev/config.yaml
|
||||
- Current: php_version: "7.4"
|
||||
- Expected: php_version: "8.2" (from composer.json)
|
||||
- Severity: High
|
||||
- Fix: Update php_version to match minimum requirement
|
||||
|
||||
- ⚠️ DDEV docroot mismatch
|
||||
- File: .ddev/config.yaml
|
||||
- Current: docroot: public
|
||||
- Expected: docroot: .Build/public (from composer.json extra.typo3.cms.web-dir)
|
||||
- Severity: High
|
||||
- Fix: Update docroot to match web-dir
|
||||
```
|
||||
|
||||
## Scoring Impact
|
||||
|
||||
**Best Practices Score Components (out of 20):**
|
||||
|
||||
| Component | Max Points | DDEV | Docker Compose | None |
|
||||
|-----------|-----------|------|----------------|------|
|
||||
| **Dev Environment Exists** | 6 | 6 | 4 | 0 |
|
||||
| **Configuration Correct** | 4 | 4 | 3 | 0 |
|
||||
| **Version Matching** | 3 | 3 | 2 | 0 |
|
||||
| **Documentation** | 2 | 2 | 1 | 0 |
|
||||
| **Custom Commands/Enhancements** | 2 | 2 | 0 | 0 |
|
||||
| **Other Best Practices** | 3 | 3 | 3 | 3 |
|
||||
| **Total** | 20 | 20 | 13 | 3 |
|
||||
|
||||
**Deductions:**
|
||||
|
||||
| Issue | Severity | Score Impact |
|
||||
|-------|----------|--------------|
|
||||
| No dev environment at all | High | -6 points |
|
||||
| PHP version mismatch | High | -3 points |
|
||||
| Docroot mismatch | High | -3 points |
|
||||
| Wrong type (not 'typo3') | Medium | -2 points |
|
||||
| Missing custom commands | Low | -1 point |
|
||||
| No documentation | Low | -1 point |
|
||||
|
||||
## Tea Extension Reference
|
||||
|
||||
**Source:** https://github.com/TYPO3BestPractices/tea/tree/main/.ddev
|
||||
|
||||
**Tea DDEV Structure:**
|
||||
|
||||
```
|
||||
.ddev/
|
||||
├── .gitignore
|
||||
├── config.yaml # Main configuration
|
||||
├── config.typo3.yaml # TYPO3-specific settings
|
||||
└── commands/
|
||||
└── web/
|
||||
├── typo3 # TYPO3 CLI wrapper
|
||||
├── test-unit # Run unit tests
|
||||
└── test-functional # Run functional tests
|
||||
```
|
||||
|
||||
**Tea config.yaml (simplified):**
|
||||
|
||||
```yaml
|
||||
name: tea
|
||||
type: typo3
|
||||
docroot: .Build/public
|
||||
php_version: "8.2"
|
||||
webserver_type: nginx-fpm
|
||||
database:
|
||||
type: mariadb
|
||||
version: "10.11"
|
||||
xdebug_enabled: false
|
||||
```
|
||||
|
||||
**Usage Examples:**
|
||||
|
||||
```bash
|
||||
# Start DDEV
|
||||
ddev start
|
||||
|
||||
# Install dependencies
|
||||
ddev composer install
|
||||
|
||||
# Run TYPO3 CLI
|
||||
ddev typo3 cache:flush
|
||||
|
||||
# Run unit tests
|
||||
ddev test-unit
|
||||
|
||||
# Run functional tests
|
||||
ddev test-functional
|
||||
|
||||
# Access database
|
||||
ddev mysql
|
||||
|
||||
# SSH into container
|
||||
ddev ssh
|
||||
```
|
||||
|
||||
## Quick Reference Checklist
|
||||
|
||||
**When evaluating development environment:**
|
||||
|
||||
```
|
||||
□ .ddev/config.yaml exists (preferred)
|
||||
□ OR docker-compose.yml exists (acceptable)
|
||||
□ OR .devcontainer/devcontainer.json exists (VS Code only)
|
||||
□ Configuration type is 'typo3' (DDEV) or uses TYPO3 image (Docker Compose)
|
||||
□ PHP version matches composer.json minimum
|
||||
□ Docroot matches composer.json web-dir
|
||||
□ Database is MariaDB 10.11+ or MySQL 8.0+
|
||||
□ Custom commands for common tasks (DDEV)
|
||||
□ Documentation exists (README.md mentions DDEV/Docker setup)
|
||||
□ .ddev/.gitignore present (excludes dynamic files)
|
||||
□ Post-start hooks run composer install (optional but nice)
|
||||
```
|
||||
|
||||
## Common Issues
|
||||
|
||||
### Issue: Empty .devcontainer/
|
||||
|
||||
**Diagnosis:**
|
||||
```bash
|
||||
ls -la .devcontainer/
|
||||
# total 8
|
||||
# drwxr-sr-x 2 user user 4096 Oct 20 20:05 .
|
||||
```
|
||||
|
||||
**Severity:** 🟢 Low (incomplete setup, doesn't help or hurt)
|
||||
|
||||
**Fix:** Either populate with devcontainer.json or remove directory
|
||||
|
||||
### Issue: DDEV but no .gitignore
|
||||
|
||||
**Diagnosis:**
|
||||
```bash
|
||||
ls -la .ddev/.gitignore
|
||||
# No such file or directory
|
||||
```
|
||||
|
||||
**Problem:** DDEV generates dynamic files that shouldn't be committed
|
||||
|
||||
**Fix:** Create `.ddev/.gitignore`:
|
||||
```
|
||||
/*.yaml
|
||||
.ddev-docker-compose-*.yaml
|
||||
.homeadditions
|
||||
.sshimagename
|
||||
commands/web/.ddev-docker-compose-*.yaml
|
||||
import-db/
|
||||
```
|
||||
|
||||
### Issue: Wrong DDEV project type
|
||||
|
||||
**Diagnosis:**
|
||||
```yaml
|
||||
# .ddev/config.yaml
|
||||
type: php # ❌ Wrong
|
||||
```
|
||||
|
||||
**Problem:** Misses TYPO3-specific optimizations (URL structure, etc.)
|
||||
|
||||
**Fix:** Change to `type: typo3`
|
||||
|
||||
## Resources
|
||||
|
||||
- **DDEV Documentation:** https://ddev.readthedocs.io/
|
||||
- **DDEV TYPO3 Quickstart:** https://ddev.readthedocs.io/en/stable/users/quickstart/#typo3
|
||||
- **Tea Extension DDEV Setup:** https://github.com/TYPO3BestPractices/tea/tree/main/.ddev
|
||||
- **TYPO3 Docker Documentation:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/LocalDevelopment/
|
||||
621
skills/typo3-conformance/references/directory-structure.md
Normal file
621
skills/typo3-conformance/references/directory-structure.md
Normal file
@@ -0,0 +1,621 @@
|
||||
# Directory Structure Standards
|
||||
|
||||
**Purpose:** Validate proper separation of committed configuration vs generated/temporary files in TYPO3 extensions
|
||||
|
||||
## Why Directory Structure Matters
|
||||
|
||||
Proper directory organization prevents common issues:
|
||||
|
||||
- ✅ **Version Control Hygiene** - Only configuration committed, not generated files
|
||||
- ✅ **Build Reproducibility** - Clean installs work without artifacts
|
||||
- ✅ **CI/CD Clarity** - Clear separation of what's tracked vs generated
|
||||
- ✅ **Onboarding Efficiency** - New contributors understand structure instantly
|
||||
- ✅ **Maintenance Simplicity** - Updates don't conflict with local artifacts
|
||||
|
||||
Without proper structure:
|
||||
- ❌ Bloated repositories with vendor/, cache files committed
|
||||
- ❌ Git conflicts in generated files
|
||||
- ❌ CI failures from missing .gitignore patterns
|
||||
- ❌ Confusion about what files are source vs generated
|
||||
|
||||
## TYPO3 Standard Directory Pattern
|
||||
|
||||
### Build/ - Committed Configuration
|
||||
|
||||
**Purpose:** Project-specific configuration files that define how to build, test, and validate code
|
||||
|
||||
**Characteristics:**
|
||||
- ✅ Committed to git
|
||||
- ✅ Shared across all developers
|
||||
- ✅ Version controlled
|
||||
- ✅ Defines project standards
|
||||
|
||||
**Standard Contents:**
|
||||
|
||||
```
|
||||
Build/
|
||||
├── phpstan.neon # Static analysis config
|
||||
├── phpstan-baseline.neon # Known issues baseline (optional)
|
||||
├── php-cs-fixer.php # Code style config
|
||||
├── rector.php # Refactoring rules (optional, can be in Build/rector/)
|
||||
├── phpunit/
|
||||
│ ├── UnitTests.xml # Unit test configuration
|
||||
│ ├── FunctionalTests.xml # Functional test configuration
|
||||
│ └── bootstrap.php # Test bootstrap (if needed)
|
||||
└── Scripts/
|
||||
└── runTests.sh # Test orchestration script
|
||||
```
|
||||
|
||||
**Alternative Rector Location:**
|
||||
```
|
||||
Build/
|
||||
└── rector/
|
||||
└── rector.php # Rector config in subdirectory
|
||||
```
|
||||
|
||||
**Git Status:** All files tracked (not in .gitignore)
|
||||
|
||||
### .Build/ - Generated/Temporary Files
|
||||
|
||||
**Purpose:** Composer-generated files, caches, runtime artifacts, test outputs
|
||||
|
||||
**Characteristics:**
|
||||
- ❌ NOT committed to git (gitignored)
|
||||
- ✅ Generated by composer/build tools
|
||||
- ✅ Recreatable from configuration
|
||||
- ✅ Developer-specific caches allowed
|
||||
|
||||
**Standard Contents:**
|
||||
|
||||
```
|
||||
.Build/
|
||||
├── bin/ # Composer bin-dir (phpunit, phpstan, etc.)
|
||||
├── public/ # Web root (TYPO3 web-dir)
|
||||
│ ├── index.php
|
||||
│ ├── typo3/
|
||||
│ └── typo3conf/
|
||||
├── vendor/ # Composer dependencies
|
||||
├── .php-cs-fixer.cache # php-cs-fixer cache file
|
||||
├── .phpunit.result.cache # PHPUnit cache
|
||||
└── var/ # Runtime cache/logs (optional)
|
||||
```
|
||||
|
||||
**Git Status:** Entire directory in .gitignore
|
||||
|
||||
**Standard .gitignore Entry:**
|
||||
|
||||
```gitignore
|
||||
# Composer generated files
|
||||
.Build/
|
||||
```
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
### 1. Build/ Directory Structure
|
||||
|
||||
**Check for committed configuration files:**
|
||||
|
||||
```bash
|
||||
ls -la Build/
|
||||
```
|
||||
|
||||
**Required files for comprehensive quality:**
|
||||
- `Build/phpstan.neon` - Static analysis configuration
|
||||
- `Build/php-cs-fixer.php` - Code style configuration
|
||||
- `Build/phpunit/UnitTests.xml` - Unit test configuration
|
||||
- `Build/Scripts/runTests.sh` - Test orchestration
|
||||
|
||||
**Optional but recommended:**
|
||||
- `Build/phpunit/FunctionalTests.xml` - Functional test configuration
|
||||
- `Build/phpstan-baseline.neon` - Known issues baseline
|
||||
- `Build/rector.php` or `Build/rector/rector.php` - Refactoring rules
|
||||
|
||||
**Severity if missing:** 🟡 **Medium** - Quality tools not standardized
|
||||
|
||||
### 2. .Build/ Directory Gitignore
|
||||
|
||||
**Check .gitignore for .Build/ exclusion:**
|
||||
|
||||
```bash
|
||||
grep -E '^\\.Build/' .gitignore
|
||||
```
|
||||
|
||||
**Expected pattern:**
|
||||
|
||||
```gitignore
|
||||
.Build/
|
||||
```
|
||||
|
||||
**Alternative patterns (also valid):**
|
||||
|
||||
```gitignore
|
||||
/.Build/
|
||||
.Build/*
|
||||
```
|
||||
|
||||
**Check for accidental commits:**
|
||||
|
||||
```bash
|
||||
git ls-files .Build/
|
||||
# Should return empty - no files from .Build/ should be tracked
|
||||
```
|
||||
|
||||
**Severity if misconfigured:** 🔴 **High** - Can lead to repository bloat
|
||||
|
||||
### 3. Composer Configuration Alignment
|
||||
|
||||
**Validate composer.json paths match directory structure:**
|
||||
|
||||
```bash
|
||||
# Extract composer paths
|
||||
cat composer.json | jq -r '.config."bin-dir"' # Should be .Build/bin
|
||||
cat composer.json | jq -r '.config."vendor-dir"' # Should be .Build/vendor
|
||||
cat composer.json | jq -r '.extra.typo3.cms."web-dir"' # Should be .Build/public
|
||||
```
|
||||
|
||||
**Expected composer.json:**
|
||||
|
||||
```json
|
||||
{
|
||||
"config": {
|
||||
"vendor-dir": ".Build/vendor",
|
||||
"bin-dir": ".Build/bin"
|
||||
},
|
||||
"extra": {
|
||||
"typo3/cms": {
|
||||
"web-dir": ".Build/public"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Severity if mismatched:** 🔴 **High** - Build will fail or create wrong structure
|
||||
|
||||
### 4. Quality Tool Configuration Paths
|
||||
|
||||
**Validate tool configs reference correct cache locations:**
|
||||
|
||||
```bash
|
||||
# Check PHPUnit cache location
|
||||
grep -r "\.phpunit\.result\.cache" Build/phpunit/*.xml
|
||||
# Should reference .Build/.phpunit.result.cache or no cache directive
|
||||
|
||||
# Check php-cs-fixer cache
|
||||
grep -r "cache-file" composer.json Build/php-cs-fixer.php
|
||||
# Should reference .Build/.php-cs-fixer.cache if specified
|
||||
|
||||
# Check PHPStan cache (usually auto-managed)
|
||||
grep -r "tmpDir" Build/phpstan.neon
|
||||
# Should reference .Build/phpstan if specified, or auto temp
|
||||
```
|
||||
|
||||
**Example CORRECT patterns:**
|
||||
|
||||
```json
|
||||
// composer.json
|
||||
{
|
||||
"scripts": {
|
||||
"ci:cgl": [
|
||||
"php-cs-fixer fix --cache-file .Build/.php-cs-fixer.cache --dry-run --diff"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```php
|
||||
// Build/php-cs-fixer.php
|
||||
return (new PhpCsFixer\Config())
|
||||
->setCacheFile('.Build/.php-cs-fixer.cache')
|
||||
->setRules([...]);
|
||||
```
|
||||
|
||||
```neon
|
||||
# Build/phpstan.neon
|
||||
parameters:
|
||||
tmpDir: .Build/phpstan
|
||||
```
|
||||
|
||||
**Severity if wrong:** 🟡 **Medium** - Works but creates clutter in Build/
|
||||
|
||||
### 5. Tea Extension Reference
|
||||
|
||||
**Source:** https://github.com/TYPO3BestPractices/tea
|
||||
|
||||
**Tea Directory Structure:**
|
||||
|
||||
```
|
||||
tea/
|
||||
├── .gitignore # Excludes .Build/
|
||||
├── Build/
|
||||
│ ├── phpstan.neon
|
||||
│ ├── phpstan-baseline.neon
|
||||
│ ├── php-cs-fixer.php
|
||||
│ ├── phpunit/
|
||||
│ │ ├── FunctionalTests.xml
|
||||
│ │ └── UnitTests.xml
|
||||
│ └── Scripts/
|
||||
│ └── runTests.sh
|
||||
├── .Build/ # Gitignored, generated by composer
|
||||
│ ├── bin/
|
||||
│ ├── public/
|
||||
│ └── vendor/
|
||||
└── composer.json # Defines .Build/ paths
|
||||
```
|
||||
|
||||
**Tea .gitignore (relevant excerpt):**
|
||||
|
||||
```gitignore
|
||||
# Composer-generated files
|
||||
.Build/
|
||||
composer.lock
|
||||
```
|
||||
|
||||
**Tea composer.json (relevant excerpt):**
|
||||
|
||||
```json
|
||||
{
|
||||
"config": {
|
||||
"vendor-dir": ".Build/vendor",
|
||||
"bin-dir": ".Build/bin"
|
||||
},
|
||||
"extra": {
|
||||
"typo3/cms": {
|
||||
"web-dir": ".Build/public"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Common Issues and Fixes
|
||||
|
||||
### Issue 1: Cache Files in Build/ Directory
|
||||
|
||||
**Diagnosis:**
|
||||
|
||||
```bash
|
||||
ls -la Build/.php-cs-fixer.cache Build/.phpunit.result.cache
|
||||
# If these files exist, they're in the WRONG location
|
||||
```
|
||||
|
||||
**Problem:** Cache files committed or in wrong directory
|
||||
|
||||
**Fix:**
|
||||
|
||||
```bash
|
||||
# Remove from wrong location
|
||||
rm Build/.php-cs-fixer.cache Build/.phpunit.result.cache
|
||||
|
||||
# Update configuration to use .Build/
|
||||
```
|
||||
|
||||
**Update php-cs-fixer config:**
|
||||
|
||||
```php
|
||||
// Build/php-cs-fixer.php
|
||||
return (new PhpCsFixer\Config())
|
||||
->setCacheFile('.Build/.php-cs-fixer.cache') // ✅ Correct location
|
||||
// ...
|
||||
```
|
||||
|
||||
**Update composer scripts:**
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"ci:cgl": [
|
||||
"php-cs-fixer fix --cache-file .Build/.php-cs-fixer.cache --dry-run --diff"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Issue 2: .Build/ Files Committed to Git
|
||||
|
||||
**Diagnosis:**
|
||||
|
||||
```bash
|
||||
git ls-files .Build/
|
||||
# Should be empty
|
||||
```
|
||||
|
||||
**Problem:** Generated files tracked in git (vendor/, bin/, etc.)
|
||||
|
||||
**Fix:**
|
||||
|
||||
```bash
|
||||
# Remove from git tracking
|
||||
git rm -r --cached .Build/
|
||||
|
||||
# Ensure .gitignore has entry
|
||||
echo ".Build/" >> .gitignore
|
||||
|
||||
# Commit the cleanup
|
||||
git add .gitignore
|
||||
git commit -m "fix: remove .Build/ from git tracking, add to .gitignore"
|
||||
```
|
||||
|
||||
### Issue 3: Missing Build/ Directory
|
||||
|
||||
**Diagnosis:**
|
||||
|
||||
```bash
|
||||
ls -la Build/
|
||||
# Directory doesn't exist
|
||||
```
|
||||
|
||||
**Problem:** No standardized quality tool configuration
|
||||
|
||||
**Fix:**
|
||||
|
||||
```bash
|
||||
# Create Build/ directory structure
|
||||
mkdir -p Build/{phpunit,Scripts}
|
||||
|
||||
# Add quality tool configs (see templates below)
|
||||
# Then commit
|
||||
git add Build/
|
||||
git commit -m "feat: add Build/ directory with quality tool configurations"
|
||||
```
|
||||
|
||||
### Issue 4: Rector in Wrong Location
|
||||
|
||||
**Diagnosis:**
|
||||
|
||||
```bash
|
||||
# Check for rector.php in project root
|
||||
ls -la rector.php
|
||||
|
||||
# Should be in Build/ or Build/rector/ instead
|
||||
```
|
||||
|
||||
**Problem:** Configuration file in project root instead of Build/
|
||||
|
||||
**Fix:**
|
||||
|
||||
```bash
|
||||
# Option 1: Move to Build/
|
||||
mv rector.php Build/rector.php
|
||||
|
||||
# Option 2: Move to Build/rector/ (preferred for complex configs)
|
||||
mkdir -p Build/rector
|
||||
mv rector.php Build/rector/rector.php
|
||||
|
||||
# Update paths in rector.php
|
||||
# Then commit
|
||||
git add Build/
|
||||
git rm rector.php
|
||||
git commit -m "refactor: move rector config to Build/ directory"
|
||||
```
|
||||
|
||||
## Conformance Report Integration
|
||||
|
||||
### When Evaluating Directory Structure:
|
||||
|
||||
**In "Best Practices" Section:**
|
||||
|
||||
```markdown
|
||||
### Directory Structure
|
||||
|
||||
**Analysis:**
|
||||
|
||||
- ✅ Build/ directory present with committed configurations
|
||||
- ✅ Build/phpstan.neon, Build/php-cs-fixer.php present
|
||||
- ✅ Build/phpunit/ directory with test configs
|
||||
- ✅ Build/Scripts/runTests.sh present
|
||||
- ✅ .Build/ properly gitignored (entire directory)
|
||||
- ✅ Composer paths correctly reference .Build/
|
||||
- ✅ Cache files located in .Build/, not Build/
|
||||
- ✅ No .Build/ files committed to git
|
||||
|
||||
**Or with issues:**
|
||||
|
||||
- ❌ Cache files in wrong location
|
||||
- Files: Build/.php-cs-fixer.cache, Build/.phpunit.result.cache
|
||||
- Expected: .Build/.php-cs-fixer.cache, .Build/.phpunit.result.cache
|
||||
- Severity: Medium
|
||||
- Fix: Move cache files to .Build/ and update configs
|
||||
|
||||
- ❌ .Build/ files committed to git
|
||||
- Files: .Build/vendor/, .Build/bin/
|
||||
- Command: `git ls-files .Build/` shows tracked files
|
||||
- Severity: High
|
||||
- Fix: `git rm -r --cached .Build/` and ensure .gitignore has `.Build/`
|
||||
|
||||
- ⚠️ Missing Build/ directory
|
||||
- Impact: No standardized quality tool configuration
|
||||
- Severity: Medium
|
||||
- Recommendation: Create Build/ with phpstan.neon, php-cs-fixer.php, phpunit configs
|
||||
```
|
||||
|
||||
## Scoring Impact
|
||||
|
||||
**Best Practices Score Deductions:**
|
||||
|
||||
| Issue | Severity | Score Impact |
|
||||
|-------|----------|--------------|
|
||||
| .Build/ files committed | High | -4 points |
|
||||
| Cache files in Build/ | Medium | -2 points |
|
||||
| Missing .gitignore for .Build/ | High | -3 points |
|
||||
| Composer paths don't match structure | High | -3 points |
|
||||
| Missing Build/ directory | Medium | -2 points |
|
||||
| Rector/configs in project root | Low | -1 point |
|
||||
|
||||
**Maximum deduction for directory issues:** -6 points (out of 20 for Best Practices)
|
||||
|
||||
## Automated Validation Script
|
||||
|
||||
Create `scripts/validate-directory-structure.sh`:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
echo "🔍 Validating directory structure..."
|
||||
|
||||
ISSUES=0
|
||||
|
||||
# Check 1: .Build/ should be gitignored
|
||||
if ! grep -qE '^\\.Build/' .gitignore; then
|
||||
echo "❌ .gitignore missing '.Build/' entry"
|
||||
ISSUES=$((ISSUES + 1))
|
||||
else
|
||||
echo "✅ .Build/ properly gitignored"
|
||||
fi
|
||||
|
||||
# Check 2: No .Build/ files should be tracked
|
||||
TRACKED_BUILD=$(git ls-files .Build/ 2>/dev/null | wc -l)
|
||||
if [ "${TRACKED_BUILD}" -gt 0 ]; then
|
||||
echo "❌ .Build/ files are committed to git:"
|
||||
git ls-files .Build/
|
||||
ISSUES=$((ISSUES + 1))
|
||||
else
|
||||
echo "✅ No .Build/ files tracked in git"
|
||||
fi
|
||||
|
||||
# Check 3: Build/ directory should exist
|
||||
if [ ! -d "Build" ]; then
|
||||
echo "⚠️ Build/ directory missing"
|
||||
ISSUES=$((ISSUES + 1))
|
||||
else
|
||||
echo "✅ Build/ directory exists"
|
||||
|
||||
# Check for standard files
|
||||
[ -f "Build/phpstan.neon" ] && echo " ✅ phpstan.neon" || echo " ⚠️ phpstan.neon missing"
|
||||
[ -f "Build/php-cs-fixer.php" ] && echo " ✅ php-cs-fixer.php" || echo " ⚠️ php-cs-fixer.php missing"
|
||||
[ -d "Build/phpunit" ] && echo " ✅ phpunit/" || echo " ⚠️ phpunit/ missing"
|
||||
[ -f "Build/Scripts/runTests.sh" ] && echo " ✅ runTests.sh" || echo " ⚠️ runTests.sh missing"
|
||||
fi
|
||||
|
||||
# Check 4: Cache files should NOT be in Build/
|
||||
if [ -f "Build/.php-cs-fixer.cache" ] || [ -f "Build/.phpunit.result.cache" ]; then
|
||||
echo "❌ Cache files in wrong location (Build/ instead of .Build/):"
|
||||
ls -la Build/.*.cache 2>/dev/null || true
|
||||
ISSUES=$((ISSUES + 1))
|
||||
else
|
||||
echo "✅ No cache files in Build/"
|
||||
fi
|
||||
|
||||
# Check 5: Composer paths should reference .Build/
|
||||
BIN_DIR=$(jq -r '.config."bin-dir" // ".Build/bin"' composer.json)
|
||||
VENDOR_DIR=$(jq -r '.config."vendor-dir" // ".Build/vendor"' composer.json)
|
||||
WEB_DIR=$(jq -r '.extra.typo3.cms."web-dir" // ".Build/public"' composer.json)
|
||||
|
||||
if [ "${BIN_DIR}" = ".Build/bin" ]; then
|
||||
echo "✅ Composer bin-dir: ${BIN_DIR}"
|
||||
else
|
||||
echo "⚠️ Composer bin-dir: ${BIN_DIR} (expected .Build/bin)"
|
||||
ISSUES=$((ISSUES + 1))
|
||||
fi
|
||||
|
||||
if [ "${VENDOR_DIR}" = ".Build/vendor" ]; then
|
||||
echo "✅ Composer vendor-dir: ${VENDOR_DIR}"
|
||||
else
|
||||
echo "⚠️ Composer vendor-dir: ${VENDOR_DIR} (expected .Build/vendor)"
|
||||
ISSUES=$((ISSUES + 1))
|
||||
fi
|
||||
|
||||
if [ "${WEB_DIR}" = ".Build/public" ]; then
|
||||
echo "✅ TYPO3 web-dir: ${WEB_DIR}"
|
||||
else
|
||||
echo "⚠️ TYPO3 web-dir: ${WEB_DIR} (expected .Build/public)"
|
||||
ISSUES=$((ISSUES + 1))
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if [ ${ISSUES} -eq 0 ]; then
|
||||
echo "✅ Directory structure validation complete - no issues found"
|
||||
exit 0
|
||||
else
|
||||
echo "❌ Directory structure validation found ${ISSUES} issue(s)"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
## Quick Reference Checklist
|
||||
|
||||
**When evaluating directory structure:**
|
||||
|
||||
```
|
||||
□ .gitignore contains .Build/ entry
|
||||
□ Build/ directory exists and contains configs
|
||||
□ Build/phpstan.neon exists
|
||||
□ Build/php-cs-fixer.php exists
|
||||
□ Build/phpunit/ directory exists with XML configs
|
||||
□ Build/Scripts/runTests.sh exists
|
||||
□ .Build/ is NOT tracked in git (git ls-files .Build/ is empty)
|
||||
□ Cache files are in .Build/, not Build/
|
||||
□ Composer bin-dir = .Build/bin
|
||||
□ Composer vendor-dir = .Build/vendor
|
||||
□ TYPO3 web-dir = .Build/public
|
||||
□ No configuration files in project root (rector.php, phpstan.neon, etc.)
|
||||
```
|
||||
|
||||
## Configuration File Templates
|
||||
|
||||
### Build/phpstan.neon
|
||||
|
||||
```neon
|
||||
includes:
|
||||
- vendor/phpstan/phpstan-strict-rules/rules.neon
|
||||
- vendor/saschaegerer/phpstan-typo3/extension.neon
|
||||
|
||||
parameters:
|
||||
level: max
|
||||
paths:
|
||||
- Classes
|
||||
- Tests
|
||||
tmpDir: .Build/phpstan
|
||||
reportUnmatchedIgnoredErrors: true
|
||||
```
|
||||
|
||||
### Build/php-cs-fixer.php
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$finder = (new PhpCsFixer\Finder())
|
||||
->in(__DIR__ . '/../Classes')
|
||||
->in(__DIR__ . '/../Tests');
|
||||
|
||||
return (new PhpCsFixer\Config())
|
||||
->setRules([
|
||||
'@PSR12' => true,
|
||||
'@PhpCsFixer' => true,
|
||||
'declare_strict_types' => true,
|
||||
])
|
||||
->setCacheFile('.Build/.php-cs-fixer.cache')
|
||||
->setRiskyAllowed(true)
|
||||
->setFinder($finder);
|
||||
```
|
||||
|
||||
### Build/rector/rector.php
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Rector\Config\RectorConfig;
|
||||
use Rector\ValueObject\PhpVersion;
|
||||
use Ssch\TYPO3Rector\Set\Typo3LevelSetList;
|
||||
|
||||
return RectorConfig::configure()
|
||||
->withPaths([
|
||||
__DIR__ . '/../../Classes/',
|
||||
__DIR__ . '/../../Tests/',
|
||||
])
|
||||
->withPhpVersion(PhpVersion::PHP_82)
|
||||
->withPhpSets(true)
|
||||
->withSets([
|
||||
Typo3LevelSetList::UP_TO_TYPO3_13,
|
||||
]);
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- **Tea Extension Structure:** https://github.com/TYPO3BestPractices/tea
|
||||
- **Composer Documentation:** https://getcomposer.org/doc/06-config.md
|
||||
- **TYPO3 Extension Structure:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/FileStructure/
|
||||
712
skills/typo3-conformance/references/excellence-indicators.md
Normal file
712
skills/typo3-conformance/references/excellence-indicators.md
Normal file
@@ -0,0 +1,712 @@
|
||||
# Excellence Indicators Reference
|
||||
|
||||
**Purpose:** Document optional features that indicate exceptional TYPO3 extension quality beyond basic conformance
|
||||
|
||||
## Overview
|
||||
|
||||
Excellence Indicators are **optional** features that demonstrate exceptional project quality, community engagement, and professional development practices. Extensions are **not penalized** for missing these features, but **earn bonus points** when present.
|
||||
|
||||
**Key Principle:** Base conformance (0-100 points) measures adherence to TYPO3 standards. Excellence indicators (0-20 bonus points) reward exceptional quality.
|
||||
|
||||
---
|
||||
|
||||
## Scoring System
|
||||
|
||||
**Total Excellence Points: 0-20 (bonus)**
|
||||
|
||||
| Category | Max Points | Purpose |
|
||||
|----------|-----------|---------|
|
||||
| Community & Internationalization | 6 | Engagement, accessibility, distribution |
|
||||
| Advanced Quality Tooling | 7 | Automation, code quality, maintenance |
|
||||
| Documentation Excellence | 4 | Comprehensive docs, modern tooling |
|
||||
| Extension Configuration | 3 | Professional setup, flexibility |
|
||||
|
||||
---
|
||||
|
||||
## Category 1: Community & Internationalization (0-6 points)
|
||||
|
||||
### 1.1 Crowdin Integration (+2 points)
|
||||
|
||||
**File:** `crowdin.yml`
|
||||
|
||||
**Purpose:** Community-driven translation management platform integration
|
||||
|
||||
**Example (georgringer/news):**
|
||||
```yaml
|
||||
files:
|
||||
- source: /Resources/Private/Language/locallang*.xlf
|
||||
translation: /Resources/Private/Language/%two_letters_code%.%original_file_name%
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Enables community translators to contribute
|
||||
- Automated translation synchronization
|
||||
- Professional multilingual support
|
||||
- Reduces maintenance burden for translations
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
[ -f "crowdin.yml" ] && echo "✅ Crowdin integration (+2)"
|
||||
```
|
||||
|
||||
**Reference:** [Crowdin TYPO3 Integration](https://crowdin.com/)
|
||||
|
||||
---
|
||||
|
||||
### 1.2 GitHub Issue Templates (+1 point)
|
||||
|
||||
**Files:** `.github/ISSUE_TEMPLATE/`
|
||||
- `Bug_report.md`
|
||||
- `Feature_request.md`
|
||||
- `Support_question.md`
|
||||
|
||||
**Purpose:** Structured community contribution and issue reporting
|
||||
|
||||
**Example (georgringer/news):**
|
||||
```markdown
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: 'bug'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
...
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Ensures complete bug reports
|
||||
- Reduces back-and-forth communication
|
||||
- Categorizes issues automatically
|
||||
- Professional project impression
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
ls -1 .github/ISSUE_TEMPLATE/*.md 2>/dev/null | wc -l
|
||||
# 3 files = +1 point
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 1.3 .gitattributes Export Optimization (+1 point)
|
||||
|
||||
**File:** `.gitattributes`
|
||||
|
||||
**Purpose:** Reduce TER (TYPO3 Extension Repository) package size by excluding development files
|
||||
|
||||
**Example (georgringer/news):**
|
||||
```gitattributes
|
||||
/.github/ export-ignore
|
||||
/Build/ export-ignore
|
||||
/Tests/ export-ignore
|
||||
/.editorconfig export-ignore
|
||||
/.gitattributes export-ignore
|
||||
/.gitignore export-ignore
|
||||
/.styleci.yml export-ignore
|
||||
/Makefile export-ignore
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Smaller download size for production installations
|
||||
- Faster `composer install` in production
|
||||
- Professional package distribution
|
||||
- Security (doesn't ship development files)
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
grep -q "export-ignore" .gitattributes && echo "✅ Export optimization (+1)"
|
||||
```
|
||||
|
||||
**Impact Example:**
|
||||
- Repository size: 15 MB (with tests, CI configs)
|
||||
- TER package size: 2 MB (production files only)
|
||||
- **Reduction:** ~87%
|
||||
|
||||
---
|
||||
|
||||
### 1.4 Professional README with Badges (+2 points)
|
||||
|
||||
**File:** `README.md`
|
||||
|
||||
**Purpose:** Comprehensive project overview with status indicators
|
||||
|
||||
**Required Elements (all 4 required for points):**
|
||||
1. Stability badge (Packagist or TER)
|
||||
2. CI/Build status badge (GitHub Actions, GitLab CI)
|
||||
3. Download stats (Packagist downloads)
|
||||
4. Compatibility matrix table
|
||||
|
||||
**Example (georgringer/news):**
|
||||
```markdown
|
||||
[](https://extensions.typo3.org/extension/news/)
|
||||
[](https://get.typo3.org/version/12)
|
||||
[](https://get.typo3.org/version/13)
|
||||
[](https://packagist.org/packages/georgringer/news)
|
||||

|
||||
[](https://crowdin.com/project/typo3-extension-news)
|
||||
|
||||
## Compatibility
|
||||
|
||||
| News | TYPO3 | PHP | Support / Development |
|
||||
|------|-----------|-----------|--------------------------------------|
|
||||
| 12 | 12 - 13 | 8.1 - 8.3 | features, bugfixes, security updates |
|
||||
| 11 | 11 - 12 | 7.4 - 8.3 | security updates |
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
# Check for at least 3 badges and a compatibility table
|
||||
grep -c "!\[" README.md # Badge count
|
||||
grep -c "^|" README.md # Table rows
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Category 2: Advanced Quality Tooling (0-7 points)
|
||||
|
||||
### 2.1 Fractor Configuration (+2 points)
|
||||
|
||||
**File:** `Build/fractor/fractor.php`
|
||||
|
||||
**Purpose:** Automated refactoring for TypoScript and XML configuration files
|
||||
|
||||
**What is Fractor?**
|
||||
- Rector handles PHP code refactoring
|
||||
- **Fractor handles TypoScript and XML** file refactoring
|
||||
- Automates TYPO3 configuration migrations
|
||||
|
||||
**Example (georgringer/news):**
|
||||
```php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use a9f\Fractor\Configuration\FractorConfiguration;
|
||||
use a9f\FractorTypoScript\Configuration\TypoScriptProcessorOption;
|
||||
use a9f\FractorXml\Configuration\XmlProcessorOption;
|
||||
use a9f\Typo3Fractor\Set\Typo3LevelSetList;
|
||||
|
||||
return FractorConfiguration::configure()
|
||||
->withPaths([
|
||||
__DIR__ . '/../../Classes',
|
||||
__DIR__ . '/../../Configuration/',
|
||||
__DIR__ . '/../../Resources',
|
||||
])
|
||||
->withSets([
|
||||
Typo3LevelSetList::UP_TO_TYPO3_12,
|
||||
])
|
||||
->withOptions([
|
||||
TypoScriptProcessorOption::INDENT_CHARACTER => 'auto',
|
||||
XmlProcessorOption::INDENT_CHARACTER => Indent::STYLE_TAB,
|
||||
]);
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Automates TypoScript configuration migrations
|
||||
- Modernizes FlexForm XML structures
|
||||
- Reduces manual refactoring effort
|
||||
- Catches TYPO3 API changes in configuration
|
||||
|
||||
**Required Packages:**
|
||||
```json
|
||||
{
|
||||
"require-dev": {
|
||||
"a9f/fractor": "^1.0",
|
||||
"a9f/typo3-fractor": "^1.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
[ -f "Build/fractor/fractor.php" ] && echo "✅ Fractor configuration (+2)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.2 TYPO3 CodingStandards Package (+1 point)
|
||||
|
||||
**File:** `Build/php-cs-fixer/php-cs-fixer.php`
|
||||
|
||||
**Purpose:** Official TYPO3 community coding standards package (not custom config)
|
||||
|
||||
**Example (georgringer/news):**
|
||||
```php
|
||||
<?php
|
||||
|
||||
use PhpCsFixer\Finder;
|
||||
use TYPO3\CodingStandards\CsFixerConfig;
|
||||
|
||||
$config = CsFixerConfig::create();
|
||||
$config->setHeader(
|
||||
'This file is part of the "news" Extension for TYPO3 CMS.
|
||||
|
||||
For the full copyright and license information, please read the
|
||||
LICENSE.txt file that was distributed with this source code.',
|
||||
true
|
||||
);
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Official TYPO3 community standards
|
||||
- Automatic copyright header injection
|
||||
- PER Coding Style (PSR-12 successor)
|
||||
- Consistent with TYPO3 core
|
||||
|
||||
**Required Package:**
|
||||
```json
|
||||
{
|
||||
"require-dev": {
|
||||
"typo3/coding-standards": "^0.5"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
grep -q "TYPO3\\\\CodingStandards" Build/php-cs-fixer/php-cs-fixer.php && echo "✅ TYPO3 CodingStandards (+1)"
|
||||
```
|
||||
|
||||
**Alternative (no points):** Custom php-cs-fixer config (still good, but not official package)
|
||||
|
||||
---
|
||||
|
||||
### 2.3 StyleCI Integration (+1 point)
|
||||
|
||||
**File:** `.styleci.yml`
|
||||
|
||||
**Purpose:** Cloud-based automatic code style checking on pull requests
|
||||
|
||||
**Example (georgringer/news):**
|
||||
```yaml
|
||||
preset: psr12
|
||||
|
||||
enabled:
|
||||
- no_unused_imports
|
||||
- ordered_imports
|
||||
- single_quote
|
||||
- short_array_syntax
|
||||
- hash_to_slash_comment
|
||||
- native_function_casing
|
||||
|
||||
finder:
|
||||
name:
|
||||
- "*.php"
|
||||
not-path:
|
||||
- ".Build"
|
||||
- "Build/php-cs-fixer"
|
||||
- "Documentation"
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Automatic PR code style checks (no local setup needed)
|
||||
- Visual code review integration
|
||||
- Reduces reviewer burden
|
||||
- Enforces consistency across contributors
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
[ -f ".styleci.yml" ] && echo "✅ StyleCI integration (+1)"
|
||||
```
|
||||
|
||||
**Note:** Alternative to local php-cs-fixer CI checks, not replacement
|
||||
|
||||
---
|
||||
|
||||
### 2.4 Makefile Task Automation (+1 point)
|
||||
|
||||
**File:** `Makefile`
|
||||
|
||||
**Purpose:** Self-documenting task automation and workflow management
|
||||
|
||||
**Example (georgringer/news):**
|
||||
```makefile
|
||||
.PHONY: help
|
||||
help: ## Displays this list of targets with descriptions
|
||||
@echo "The following commands are available:\n"
|
||||
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[32m%-30s\033[0m %s\n", $$1, $$2}'
|
||||
|
||||
.PHONY: docs
|
||||
docs: ## Generate projects docs (from "Documentation" directory)
|
||||
mkdir -p Documentation-GENERATED-temp
|
||||
docker run --rm --pull always -v "$(shell pwd)":/project -t ghcr.io/typo3-documentation/render-guides:latest --config=Documentation
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Discoverable commands (`make help`)
|
||||
- Consistent workflow across contributors
|
||||
- Reduces documentation for common tasks
|
||||
- Docker-based documentation rendering
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
[ -f "Makefile" ] && grep -q "^help:.*##" Makefile && echo "✅ Makefile automation (+1)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.5 Comprehensive CI Matrix (+2 points)
|
||||
|
||||
**Files:** `.github/workflows/*.yml` or `.gitlab-ci.yml`
|
||||
|
||||
**Purpose:** Test across multiple PHP versions and dependency scenarios
|
||||
|
||||
**Required for +2 points:**
|
||||
- At least 3 PHP versions tested
|
||||
- Both `composerInstallLowest` and `composerInstallHighest` strategies
|
||||
- Multiple TYPO3 versions if extension supports multiple
|
||||
|
||||
**Example (georgringer/news):**
|
||||
```yaml
|
||||
name: core 12
|
||||
on: [ push, pull_request ]
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: [ '8.1', '8.2', '8.3', '8.4' ] # 4 PHP versions
|
||||
composerInstall: [ 'composerInstallLowest', 'composerInstallHighest' ]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Install testing system
|
||||
run: Build/Scripts/runTests.sh -t 12 -p ${{ matrix.php }} -s ${{ matrix.composerInstall }}
|
||||
- name: Lint PHP
|
||||
run: Build/Scripts/runTests.sh -t 12 -p ${{ matrix.php }} -s lint
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Catches dependency conflicts early
|
||||
- Ensures compatibility across PHP versions
|
||||
- Tests minimum and maximum dependency versions
|
||||
- Professional CI/CD setup
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
# Check for matrix with multiple PHP versions and composerInstall strategies
|
||||
grep -A 5 "matrix:" .github/workflows/*.yml | grep -c "composerInstall"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Category 3: Documentation Excellence (0-4 points)
|
||||
|
||||
### 3.1 Extensive RST Documentation (100+ files) (+3 points)
|
||||
|
||||
**Directory:** `Documentation/`
|
||||
|
||||
**Purpose:** Comprehensive, structured documentation covering all aspects
|
||||
|
||||
**Example (georgringer/news: 183 RST files):**
|
||||
```
|
||||
Documentation/
|
||||
├── Addons/ # Extension integrations
|
||||
├── Administration/ # Backend administration
|
||||
├── Introduction/ # Getting started
|
||||
├── QuickStart/ # Fast setup guide
|
||||
├── Reference/ # API reference
|
||||
├── Tutorials/ # Step-by-step guides
|
||||
├── UsersManual/ # End-user documentation
|
||||
└── Images/ # Visual assets
|
||||
```
|
||||
|
||||
**Scoring:**
|
||||
- 50-99 RST files: +1 point
|
||||
- 100-149 RST files: +2 points
|
||||
- 150+ RST files: +3 points
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
RST_COUNT=$(find Documentation -name "*.rst" | wc -l)
|
||||
if [ $RST_COUNT -ge 150 ]; then
|
||||
echo "✅ Extensive documentation 150+ RST (+3)"
|
||||
elif [ $RST_COUNT -ge 100 ]; then
|
||||
echo "✅ Comprehensive documentation 100+ RST (+2)"
|
||||
elif [ $RST_COUNT -ge 50 ]; then
|
||||
echo "✅ Good documentation 50+ RST (+1)"
|
||||
fi
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Reduces support burden
|
||||
- Improves onboarding
|
||||
- Professional project impression
|
||||
- Better community adoption
|
||||
|
||||
---
|
||||
|
||||
### 3.2 Modern Documentation Tooling (+1 point)
|
||||
|
||||
**Files:**
|
||||
- `Documentation/guides.xml`
|
||||
- `Documentation/screenshots.json`
|
||||
|
||||
**Purpose:** Modern TYPO3 documentation rendering and screenshot management
|
||||
|
||||
**Example (georgringer/news):**
|
||||
```xml
|
||||
<!-- guides.xml -->
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<guides xmlns="https://guides.typo3.org/ns/1.0">
|
||||
<project>
|
||||
<title>News System</title>
|
||||
<release>12.0</release>
|
||||
<vendor>georgringer</vendor>
|
||||
</project>
|
||||
</guides>
|
||||
```
|
||||
|
||||
```json
|
||||
// screenshots.json
|
||||
{
|
||||
"screenshots": [
|
||||
{
|
||||
"file": "Images/Administration/BackendModule.png",
|
||||
"caption": "News administration module"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Automated documentation rendering
|
||||
- Screenshot management and regeneration
|
||||
- Consistent with TYPO3 documentation standards
|
||||
- Future-proof documentation setup
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
[ -f "Documentation/guides.xml" ] && echo "✅ Modern documentation tooling (+1)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Category 4: Extension Configuration (0-3 points)
|
||||
|
||||
### 4.1 Extension Configuration Template (+1 point)
|
||||
|
||||
**File:** `ext_conf_template.txt`
|
||||
|
||||
**Purpose:** Backend extension configuration interface with categorized settings
|
||||
|
||||
**Example (georgringer/news):**
|
||||
```
|
||||
# Records
|
||||
###########################
|
||||
# cat=records/enable/103; type=boolean; label=LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:extmng.prependAtCopy
|
||||
prependAtCopy = 1
|
||||
|
||||
# cat=records/enable/101; type=string; label=LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:extmng.tagPid
|
||||
tagPid = 1
|
||||
|
||||
# cat=records/enable/26; type=boolean; label=LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:extmng.rteForTeaser
|
||||
rteForTeaser = 0
|
||||
|
||||
# Backend module
|
||||
# cat=backend module/enable/10; type=boolean; label=LLL:EXT:news/Resources/Private/Language/locallang_be.xlf:extmng.showAdministrationModule
|
||||
showAdministrationModule = 1
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- User-friendly backend configuration
|
||||
- Categorized settings for clarity
|
||||
- Localized labels
|
||||
- No PHP knowledge required for configuration
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
[ -f "ext_conf_template.txt" ] && echo "✅ Extension configuration template (+1)"
|
||||
```
|
||||
|
||||
**Note:** Not required for modern TYPO3 extensions using Site Sets, but still valuable for global extension settings
|
||||
|
||||
---
|
||||
|
||||
### 4.2 Composer Documentation Scripts (+1 point)
|
||||
|
||||
**File:** `composer.json`
|
||||
|
||||
**Purpose:** Automated documentation rendering and watching
|
||||
|
||||
**Required Scripts (at least 2 of 3):**
|
||||
- `doc-init` - Initialize documentation rendering
|
||||
- `doc-make` - Render documentation
|
||||
- `doc-watch` - Watch and auto-render documentation
|
||||
|
||||
**Example (georgringer/news):**
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"doc-init": "docker run --rm --pull always -v $(pwd):/project -it ghcr.io/typo3-documentation/render-guides:latest --config=Documentation",
|
||||
"doc-make": "make docs",
|
||||
"doc-watch": "docker run --rm -it --pull always -v \"./Documentation:/project/Documentation\" -v \"./Documentation-GENERATED-temp:/project/Documentation-GENERATED-temp\" -p 5173:5173 ghcr.io/garvinhicking/typo3-documentation-browsersync:latest"
|
||||
},
|
||||
"scripts-descriptions": {
|
||||
"doc-init": "Initialize documentation rendering",
|
||||
"doc-make": "Render documentation",
|
||||
"doc-watch": "Render documentation including a watcher"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Easy documentation development
|
||||
- Live preview during writing
|
||||
- Docker-based (no local dependencies)
|
||||
- Consistent with TYPO3 documentation workflow
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
grep -q "doc-init.*doc-make" composer.json && echo "✅ Composer doc scripts (+1)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.3 Multiple Configuration Sets (TYPO3 13) (+1 point)
|
||||
|
||||
**Directory:** `Configuration/Sets/`
|
||||
|
||||
**Purpose:** Multiple configuration presets for different use cases
|
||||
|
||||
**Required:** At least 2 different Sets (not just one default)
|
||||
|
||||
**Example (georgringer/news has 5 Sets):**
|
||||
```
|
||||
Configuration/Sets/
|
||||
├── News/ # Base news functionality
|
||||
├── RecordLinks/ # Record link handling
|
||||
├── Sitemap/ # Sitemap generation
|
||||
├── Twb4/ # Twitter Bootstrap 4 templates
|
||||
└── Twb5/ # Twitter Bootstrap 5 templates
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Quick setup for different scenarios
|
||||
- Reusable configuration patterns
|
||||
- Modern TYPO3 13 architecture
|
||||
- Flexible deployment
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
SET_COUNT=$(find Configuration/Sets -mindepth 1 -maxdepth 1 -type d | wc -l)
|
||||
[ $SET_COUNT -ge 2 ] && echo "✅ Multiple configuration Sets (+1)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Excellence Indicators Conformance Report Format
|
||||
|
||||
### Example Report Section
|
||||
|
||||
```markdown
|
||||
## Excellence Indicators (Bonus Score: 12/20)
|
||||
|
||||
### ✅ Community & Internationalization (4/6)
|
||||
- ✅ Crowdin integration (crowdin.yml): +2 points
|
||||
- ✅ GitHub issue templates (3 templates): +1 point
|
||||
- ❌ .gitattributes export optimization: 0 points
|
||||
- ✅ Professional README with badges: +2 points
|
||||
- Stability badge: ✅
|
||||
- CI status badge: ✅
|
||||
- Download stats: ✅
|
||||
- Compatibility matrix: ✅
|
||||
|
||||
### ✅ Advanced Quality Tooling (5/7)
|
||||
- ✅ Fractor configuration (Build/fractor/fractor.php): +2 points
|
||||
- ❌ TYPO3 CodingStandards package: 0 points (uses custom config)
|
||||
- ✅ StyleCI integration (.styleci.yml): +1 point
|
||||
- ❌ Makefile automation: 0 points
|
||||
- ✅ Comprehensive CI matrix (4 PHP versions, composerInstallLowest/Highest): +2 points
|
||||
|
||||
### ✅ Documentation Excellence (3/4)
|
||||
- ✅ Extensive documentation (183 RST files): +3 points
|
||||
- ❌ Modern documentation tooling (guides.xml): 0 points
|
||||
|
||||
### ❌ Extension Configuration (0/3)
|
||||
- ❌ ext_conf_template.txt: 0 points
|
||||
- ❌ Composer documentation scripts: 0 points
|
||||
- ❌ Multiple Configuration Sets: 0 points (only 1 Set present)
|
||||
|
||||
### Summary
|
||||
This extension demonstrates exceptional quality in documentation and CI/CD practices. Consider adding:
|
||||
- .gitattributes with export-ignore for smaller TER packages
|
||||
- TYPO3 CodingStandards package for official community standards
|
||||
- Makefile for task automation
|
||||
- Modern documentation tooling (guides.xml, screenshots.json)
|
||||
- Extension configuration template for backend settings
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference Validation Checklist
|
||||
|
||||
**When evaluating excellence indicators:**
|
||||
|
||||
```
|
||||
Community & Internationalization (0-6):
|
||||
□ crowdin.yml present (+2)
|
||||
□ 3 GitHub issue templates (+1)
|
||||
□ .gitattributes with export-ignore (+1)
|
||||
□ README with 4+ badges + compatibility table (+2)
|
||||
|
||||
Advanced Quality Tooling (0-7):
|
||||
□ Build/fractor/fractor.php present (+2)
|
||||
□ TYPO3\CodingStandards in php-cs-fixer config (+1)
|
||||
□ .styleci.yml present (+1)
|
||||
□ Makefile with help target (+1)
|
||||
□ CI matrix: 3+ PHP versions + composerInstall variants (+2)
|
||||
|
||||
Documentation Excellence (0-4):
|
||||
□ 50-99 RST files (+1) / 100-149 (+2) / 150+ (+3)
|
||||
□ guides.xml + screenshots.json (+1)
|
||||
|
||||
Extension Configuration (0-3):
|
||||
□ ext_conf_template.txt present (+1)
|
||||
□ Composer doc scripts (doc-init, doc-make, doc-watch) (+1)
|
||||
□ 2+ Configuration Sets in Configuration/Sets/ (+1)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
**For Conformance Skill:**
|
||||
|
||||
1. **Never penalize** missing excellence indicators
|
||||
2. **Always report** excellence indicators separately from base conformance
|
||||
3. **Score format:** `Base: 94/100 | Excellence: 12/20 | Total: 106/120`
|
||||
4. **Optional evaluation:** Can be disabled with flag if user only wants base conformance
|
||||
|
||||
**Example CLI:**
|
||||
```bash
|
||||
# Full evaluation (base + excellence)
|
||||
check-conformance --with-excellence
|
||||
|
||||
# Base conformance only
|
||||
check-conformance
|
||||
|
||||
# Excellence only (for established extensions)
|
||||
check-conformance --excellence-only
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- **georgringer/news:** https://github.com/georgringer/news (primary reference for excellence patterns)
|
||||
- **TYPO3 Best Practices (Tea):** https://github.com/TYPO3BestPractices/tea (primary reference for base conformance)
|
||||
- **Fractor:** https://github.com/andreaswolf/fractor
|
||||
- **TYPO3 CodingStandards:** https://github.com/TYPO3/coding-standards
|
||||
- **StyleCI:** https://styleci.io/
|
||||
- **Crowdin:** https://crowdin.com/
|
||||
610
skills/typo3-conformance/references/ext-emconf-validation.md
Normal file
610
skills/typo3-conformance/references/ext-emconf-validation.md
Normal file
@@ -0,0 +1,610 @@
|
||||
# ext_emconf.php Validation Standards (TYPO3 v13)
|
||||
|
||||
**Source:** TYPO3 Core API Reference v13.4 - FileStructure/ExtEmconf.html
|
||||
**Purpose:** Complete validation rules for ext_emconf.php including critical TER restrictions
|
||||
|
||||
## CRITICAL RESTRICTIONS
|
||||
|
||||
### ❌ MUST NOT use declare(strict_types=1)
|
||||
|
||||
**CRITICAL:** The TYPO3 Extension Repository (TER) upload **WILL FAIL** if `declare(strict_types=1)` is present in ext_emconf.php.
|
||||
|
||||
❌ **WRONG:**
|
||||
```php
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
$EM_CONF[$_EXTKEY] = [
|
||||
// configuration
|
||||
];
|
||||
```
|
||||
|
||||
✅ **CORRECT:**
|
||||
```php
|
||||
<?php
|
||||
$EM_CONF[$_EXTKEY] = [
|
||||
// configuration
|
||||
];
|
||||
```
|
||||
|
||||
**Detection:**
|
||||
```bash
|
||||
grep "declare(strict_types" ext_emconf.php && echo "❌ CRITICAL: TER upload will FAIL" || echo "✅ No strict_types"
|
||||
```
|
||||
|
||||
### ✅ MUST use $_EXTKEY variable
|
||||
|
||||
Extensions must reference the global `$_EXTKEY` variable, not hardcode the extension key.
|
||||
|
||||
❌ **WRONG:**
|
||||
```php
|
||||
$EM_CONF['my_extension'] = [
|
||||
// configuration
|
||||
];
|
||||
```
|
||||
|
||||
✅ **CORRECT:**
|
||||
```php
|
||||
$EM_CONF[$_EXTKEY] = [
|
||||
// configuration
|
||||
];
|
||||
```
|
||||
|
||||
**Detection:**
|
||||
```bash
|
||||
grep '\$EM_CONF\[$_EXTKEY\]' ext_emconf.php && echo "✅ Uses $_EXTKEY" || echo "❌ Hardcoded key"
|
||||
```
|
||||
|
||||
### ❌ MUST NOT contain custom code
|
||||
|
||||
The ext_emconf.php file must only contain the `$EM_CONF` array assignment. No additional functions, classes, or executable code is allowed.
|
||||
|
||||
❌ **WRONG:**
|
||||
```php
|
||||
<?php
|
||||
function getVersion() { return '1.0.0'; }
|
||||
$EM_CONF[$_EXTKEY] = [
|
||||
'version' => getVersion(),
|
||||
];
|
||||
```
|
||||
|
||||
❌ **WRONG:**
|
||||
```php
|
||||
<?php
|
||||
$EM_CONF[$_EXTKEY] = [
|
||||
'title' => 'My Extension',
|
||||
];
|
||||
// Additional initialization code
|
||||
require_once 'setup.php';
|
||||
```
|
||||
|
||||
✅ **CORRECT:**
|
||||
```php
|
||||
<?php
|
||||
$EM_CONF[$_EXTKEY] = [
|
||||
'title' => 'My Extension',
|
||||
'version' => '1.0.0',
|
||||
];
|
||||
```
|
||||
|
||||
**Detection:**
|
||||
```bash
|
||||
# Check for function/class definitions
|
||||
grep -E '^(function|class|interface|trait|require|include)' ext_emconf.php && echo "❌ Contains custom code" || echo "✅ No custom code"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Mandatory Fields
|
||||
|
||||
### title
|
||||
**Format:** English extension name
|
||||
|
||||
**Example:**
|
||||
```php
|
||||
'title' => 'My Extension',
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
grep "'title' =>" ext_emconf.php && echo "✅ Has title" || echo "❌ Missing title"
|
||||
```
|
||||
|
||||
### description
|
||||
**Format:** Short, precise English description of extension functionality
|
||||
|
||||
**Requirements:**
|
||||
- Clear explanation of what the extension does
|
||||
- Should identify the vendor/company for professional extensions
|
||||
- Must be meaningful (not just "Extension" or empty)
|
||||
- Keep concise but informative (typically 50-150 characters)
|
||||
|
||||
**Good Examples:**
|
||||
```php
|
||||
'description' => 'Adds image support to CKEditor5 RTE - by Netresearch',
|
||||
'description' => 'Provides advanced content management features for TYPO3 editors',
|
||||
'description' => 'Custom form elements for newsletter subscription by Vendor GmbH',
|
||||
```
|
||||
|
||||
**Bad Examples:**
|
||||
```php
|
||||
'description' => '', // Empty
|
||||
'description' => 'Extension', // Too vague
|
||||
'description' => 'Some tools', // Meaningless
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
# Check description exists
|
||||
grep "'description' =>" ext_emconf.php && echo "✅ Has description" || echo "❌ Missing description"
|
||||
|
||||
# Check description is not empty or trivial
|
||||
DESC=$(grep -oP "'description' => '\K[^']+(?=')" ext_emconf.php)
|
||||
[[ ${#DESC} -gt 20 ]] && echo "✅ Description is meaningful" || echo "⚠️ Description too short or vague"
|
||||
```
|
||||
|
||||
### version
|
||||
**Format:** `[int].[int].[int]` (semantic versioning)
|
||||
|
||||
**Examples:**
|
||||
- `1.0.0` ✅
|
||||
- `2.5.12` ✅
|
||||
- `v1.0.0` ❌ (no 'v' prefix)
|
||||
- `1.0` ❌ (must have three parts)
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
grep -oP "'version' => '\K[0-9]+\.[0-9]+\.[0-9]+" ext_emconf.php && echo "✅ Valid version format" || echo "❌ Invalid version"
|
||||
```
|
||||
|
||||
### author
|
||||
**Format:** Developer name(s), comma-separated for multiple
|
||||
|
||||
**Example:**
|
||||
```php
|
||||
'author' => 'Sebastian Koschel, Sebastian Mendel, Rico Sonntag',
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
grep "'author' =>" ext_emconf.php && echo "✅ Has author" || echo "❌ Missing author"
|
||||
```
|
||||
|
||||
### author_email
|
||||
**Format:** Email address(es), comma-separated for multiple
|
||||
|
||||
**Example:**
|
||||
```php
|
||||
'author_email' => 'developer@company.com, other@company.com',
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
grep "'author_email' =>" ext_emconf.php | grep -q "@" && echo "✅ Has author_email" || echo "❌ Missing author_email"
|
||||
```
|
||||
|
||||
### author_company
|
||||
**Format:** Company name
|
||||
|
||||
**Example:**
|
||||
```php
|
||||
'author_company' => 'Company Name GmbH',
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
grep "'author_company' =>" ext_emconf.php && echo "✅ Has author_company" || echo "⚠️ Missing author_company"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Category Options
|
||||
|
||||
**Valid categories:**
|
||||
|
||||
| Category | Purpose |
|
||||
|----------|---------|
|
||||
| `be` | Backend-oriented functionality |
|
||||
| `module` | Backend modules |
|
||||
| `fe` | Frontend-oriented functionality |
|
||||
| `plugin` | Frontend plugins |
|
||||
| `misc` | Miscellaneous utilities |
|
||||
| `services` | TYPO3 services |
|
||||
| `templates` | Website templates |
|
||||
| `example` | Example/demonstration extensions |
|
||||
| `doc` | Documentation |
|
||||
| `distribution` | Full site distributions/kickstarters |
|
||||
|
||||
**Example:**
|
||||
```php
|
||||
'category' => 'fe',
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
grep -oP "'category' => '\K[a-z]+(?=')" ext_emconf.php | grep -qE '^(be|module|fe|plugin|misc|services|templates|example|doc|distribution)$' && echo "✅ Valid category" || echo "❌ Invalid category"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## State Values
|
||||
|
||||
**Valid states:**
|
||||
|
||||
| State | Meaning |
|
||||
|-------|---------|
|
||||
| `alpha` | Initial development phase, unstable |
|
||||
| `beta` | Functional but incomplete or under active development |
|
||||
| `stable` | Production-ready (author commits to maintenance) |
|
||||
| `experimental` | Exploratory work, may be abandoned |
|
||||
| `test` | Demonstration or testing purposes only |
|
||||
| `obsolete` | Deprecated or unmaintained |
|
||||
| `excludeFromUpdates` | Prevents Extension Manager from updating |
|
||||
|
||||
**Example:**
|
||||
```php
|
||||
'state' => 'stable',
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
grep -oP "'state' => '\K[a-z]+(?=')" ext_emconf.php | grep -qE '^(alpha|beta|stable|experimental|test|obsolete|excludeFromUpdates)$' && echo "✅ Valid state" || echo "❌ Invalid state"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Constraints Structure
|
||||
|
||||
### Format
|
||||
```php
|
||||
'constraints' => [
|
||||
'depends' => [
|
||||
'typo3' => '13.4.0-13.4.99',
|
||||
'php' => '8.2.0-8.4.99',
|
||||
],
|
||||
'conflicts' => [
|
||||
'incompatible_ext' => '',
|
||||
],
|
||||
'suggests' => [
|
||||
'recommended_ext' => '1.0.0-2.99.99',
|
||||
],
|
||||
],
|
||||
```
|
||||
|
||||
### depends
|
||||
**Purpose:** Required dependencies loaded before this extension
|
||||
|
||||
**Mandatory entries:**
|
||||
- `typo3` - TYPO3 version range
|
||||
- `php` - PHP version range
|
||||
|
||||
**Example:**
|
||||
```php
|
||||
'depends' => [
|
||||
'typo3' => '12.4.0-13.4.99',
|
||||
'php' => '8.1.0-8.4.99',
|
||||
'fluid' => '12.4.0-13.4.99',
|
||||
],
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
grep -A 5 "'depends' =>" ext_emconf.php | grep -q "'typo3'" && echo "✅ TYPO3 dependency" || echo "❌ Missing TYPO3 dep"
|
||||
grep -A 5 "'depends' =>" ext_emconf.php | grep -q "'php'" && echo "✅ PHP dependency" || echo "❌ Missing PHP dep"
|
||||
```
|
||||
|
||||
### conflicts
|
||||
**Purpose:** Extensions incompatible with this one
|
||||
|
||||
**Example:**
|
||||
```php
|
||||
'conflicts' => [
|
||||
'old_extension' => '',
|
||||
],
|
||||
```
|
||||
|
||||
### suggests
|
||||
**Purpose:** Recommended companion extensions (loaded before current extension)
|
||||
|
||||
**Example:**
|
||||
```php
|
||||
'suggests' => [
|
||||
'news' => '12.1.0-12.99.99',
|
||||
],
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Version Constraint Format
|
||||
|
||||
### TYPO3 Version
|
||||
**Format:** `major.minor.patch-major.minor.patch`
|
||||
|
||||
**Examples:**
|
||||
- `12.4.0-12.4.99` - TYPO3 12 LTS only
|
||||
- `13.4.0-13.4.99` - TYPO3 13 LTS only
|
||||
- `12.4.0-13.4.99` - Both v12 and v13 (recommended for compatibility)
|
||||
|
||||
### PHP Version
|
||||
**Format:** `major.minor.patch-major.minor.patch`
|
||||
|
||||
**TYPO3 Compatibility:**
|
||||
- TYPO3 12 LTS: PHP 8.1-8.4
|
||||
- TYPO3 13 LTS: PHP 8.2-8.4
|
||||
|
||||
**Example:**
|
||||
```php
|
||||
'php' => '8.1.0-8.4.99', // For v12/v13 compatibility
|
||||
'php' => '8.2.0-8.4.99', // For v13 only
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Synchronization with composer.json
|
||||
|
||||
**Critical:** ext_emconf.php and composer.json must have matching constraints.
|
||||
|
||||
### Mapping Table
|
||||
|
||||
| composer.json | ext_emconf.php | Example |
|
||||
|--------------|----------------|---------|
|
||||
| `"typo3/cms-core": "^12.4 \|\| ^13.4"` | `'typo3' => '12.4.0-13.4.99'` | TYPO3 version |
|
||||
| `"php": "^8.1"` | `'php' => '8.1.0-8.4.99'` | PHP version |
|
||||
| `"typo3/cms-fluid": "^12.4"` | `'fluid' => '12.4.0-12.4.99'` | Extension dependency |
|
||||
|
||||
### Validation Strategy
|
||||
```bash
|
||||
# Compare TYPO3 versions
|
||||
COMPOSER_TYPO3=$(jq -r '.require."typo3/cms-core"' composer.json)
|
||||
EMCONF_TYPO3=$(grep -oP "'typo3' => '\K[0-9.-]+" ext_emconf.php)
|
||||
echo "Composer: $COMPOSER_TYPO3"
|
||||
echo "ext_emconf: $EMCONF_TYPO3"
|
||||
# Manual comparison required for ^x.y vs x.y.z-x.y.z format
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Complete Required Fields Checklist
|
||||
|
||||
**Mandatory (MUST have):**
|
||||
- [ ] `title` - Extension name in English
|
||||
- [ ] `description` - Short, precise description
|
||||
- [ ] `version` - Semantic version (x.y.z format)
|
||||
- [ ] `category` - Valid category (be, fe, plugin, misc, etc.)
|
||||
- [ ] `state` - Valid state (stable, beta, alpha, etc.)
|
||||
- [ ] `constraints.depends.typo3` - TYPO3 version range
|
||||
- [ ] `constraints.depends.php` - PHP version range
|
||||
|
||||
**Recommended (SHOULD have):**
|
||||
- [ ] `author` - Developer name(s)
|
||||
- [ ] `author_email` - Contact email(s)
|
||||
- [ ] `author_company` - Company name
|
||||
- [ ] `constraints.conflicts` - Conflicting extensions (even if empty array)
|
||||
- [ ] `constraints.suggests` - Suggested companion extensions
|
||||
|
||||
**Complete Example:**
|
||||
```php
|
||||
<?php
|
||||
$EM_CONF[$_EXTKEY] = [
|
||||
'title' => 'My Extension Title',
|
||||
'description' => 'Provides specific functionality for TYPO3.',
|
||||
'category' => 'fe',
|
||||
'author' => 'Developer Name',
|
||||
'author_email' => 'developer@company.com',
|
||||
'author_company' => 'Company Name GmbH',
|
||||
'state' => 'stable',
|
||||
'version' => '1.0.0',
|
||||
'constraints' => [
|
||||
'depends' => [
|
||||
'typo3' => '12.4.0-13.4.99',
|
||||
'php' => '8.1.0-8.4.99',
|
||||
],
|
||||
'conflicts' => [],
|
||||
'suggests' => [],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Complete Validation Script
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# validate-ext-emconf.sh
|
||||
|
||||
ERRORS=0
|
||||
WARNINGS=0
|
||||
|
||||
echo "=== ext_emconf.php Validation ===="
|
||||
echo ""
|
||||
|
||||
# CRITICAL: Check for strict_types
|
||||
if grep -q "declare(strict_types" ext_emconf.php 2>/dev/null; then
|
||||
echo "❌ CRITICAL: ext_emconf.php has declare(strict_types=1)"
|
||||
echo " TER upload will FAIL!"
|
||||
((ERRORS++))
|
||||
fi
|
||||
|
||||
# CRITICAL: Check for $_EXTKEY usage
|
||||
if ! grep -q '\$EM_CONF\[$_EXTKEY\]' ext_emconf.php 2>/dev/null; then
|
||||
echo "❌ CRITICAL: Must use \$EM_CONF[\$_EXTKEY], not hardcoded key"
|
||||
((ERRORS++))
|
||||
fi
|
||||
|
||||
# CRITICAL: Check for custom code
|
||||
if grep -E '^(function|class|interface|trait|require|include)' ext_emconf.php 2>/dev/null; then
|
||||
echo "❌ CRITICAL: ext_emconf.php contains custom code (functions/classes/requires)"
|
||||
((ERRORS++))
|
||||
fi
|
||||
|
||||
# Check mandatory fields
|
||||
grep -q "'title' =>" ext_emconf.php || { echo "❌ Missing title"; ((ERRORS++)); }
|
||||
grep -q "'description' =>" ext_emconf.php || { echo "❌ Missing description"; ((ERRORS++)); }
|
||||
grep -qP "'version' => '[0-9]+\.[0-9]+\.[0-9]+" ext_emconf.php || { echo "❌ Missing or invalid version"; ((ERRORS++)); }
|
||||
|
||||
# Check description is meaningful (>20 chars)
|
||||
DESC=$(grep -oP "'description' => '\K[^']+(?=')" ext_emconf.php)
|
||||
[[ ${#DESC} -lt 20 ]] && { echo "⚠️ Description too short (should be >20 chars)"; ((WARNINGS++)); }
|
||||
|
||||
# Check category
|
||||
CATEGORY=$(grep -oP "'category' => '\K[a-z]+(?=')" ext_emconf.php)
|
||||
if [[ ! "$CATEGORY" =~ ^(be|module|fe|plugin|misc|services|templates|example|doc|distribution)$ ]]; then
|
||||
echo "❌ Invalid category: $CATEGORY"
|
||||
((ERRORS++))
|
||||
fi
|
||||
|
||||
# Check state
|
||||
STATE=$(grep -oP "'state' => '\K[a-z]+(?=')" ext_emconf.php)
|
||||
if [[ ! "$STATE" =~ ^(alpha|beta|stable|experimental|test|obsolete|excludeFromUpdates)$ ]]; then
|
||||
echo "❌ Invalid state: $STATE"
|
||||
((ERRORS++))
|
||||
fi
|
||||
|
||||
# Check constraints
|
||||
grep -A 5 "'depends' =>" ext_emconf.php | grep -q "'typo3'" || { echo "❌ Missing TYPO3 dependency"; ((ERRORS++)); }
|
||||
grep -A 5 "'depends' =>" ext_emconf.php | grep -q "'php'" || { echo "❌ Missing PHP dependency"; ((ERRORS++)); }
|
||||
|
||||
# Check recommended author fields
|
||||
grep -q "'author' =>" ext_emconf.php || { echo "⚠️ Missing author"; ((WARNINGS++)); }
|
||||
grep "'author_email' =>" ext_emconf.php | grep -q "@" || { echo "⚠️ Missing or invalid author_email"; ((WARNINGS++)); }
|
||||
grep -q "'author_company' =>" ext_emconf.php || { echo "⚠️ Missing author_company"; ((WARNINGS++)); }
|
||||
|
||||
echo ""
|
||||
echo "Validation complete: $ERRORS errors, $WARNINGS warnings"
|
||||
exit $ERRORS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Violations and Fixes
|
||||
|
||||
### 1. Using declare(strict_types=1)
|
||||
|
||||
❌ **WRONG - TER upload FAILS:**
|
||||
```php
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
$EM_CONF[$_EXTKEY] = [
|
||||
'title' => 'My Extension',
|
||||
];
|
||||
```
|
||||
|
||||
✅ **CORRECT:**
|
||||
```php
|
||||
<?php
|
||||
$EM_CONF[$_EXTKEY] = [
|
||||
'title' => 'My Extension',
|
||||
];
|
||||
```
|
||||
|
||||
### 2. Hardcoded Extension Key
|
||||
|
||||
❌ **WRONG:**
|
||||
```php
|
||||
$EM_CONF['my_extension'] = [
|
||||
'title' => 'My Extension',
|
||||
];
|
||||
```
|
||||
|
||||
✅ **CORRECT:**
|
||||
```php
|
||||
$EM_CONF[$_EXTKEY] = [
|
||||
'title' => 'My Extension',
|
||||
];
|
||||
```
|
||||
|
||||
### 3. Invalid Category
|
||||
|
||||
❌ **WRONG:**
|
||||
```php
|
||||
'category' => 'utility', // Not a valid category
|
||||
```
|
||||
|
||||
✅ **CORRECT:**
|
||||
```php
|
||||
'category' => 'misc', // Use 'misc' for utilities
|
||||
```
|
||||
|
||||
### 4. Invalid Version Format
|
||||
|
||||
❌ **WRONG:**
|
||||
```php
|
||||
'version' => 'v1.0.0', // No 'v' prefix
|
||||
'version' => '1.0', // Must have 3 parts
|
||||
```
|
||||
|
||||
✅ **CORRECT:**
|
||||
```php
|
||||
'version' => '1.0.0',
|
||||
```
|
||||
|
||||
### 5. Missing PHP/TYPO3 Constraints
|
||||
|
||||
❌ **WRONG:**
|
||||
```php
|
||||
'constraints' => [
|
||||
'depends' => [
|
||||
'extbase' => '12.4.0-12.4.99',
|
||||
],
|
||||
],
|
||||
```
|
||||
|
||||
✅ **CORRECT:**
|
||||
```php
|
||||
'constraints' => [
|
||||
'depends' => [
|
||||
'typo3' => '12.4.0-13.4.99',
|
||||
'php' => '8.1.0-8.4.99',
|
||||
'extbase' => '12.4.0-12.4.99',
|
||||
],
|
||||
],
|
||||
```
|
||||
|
||||
### 6. Mismatched composer.json Constraints
|
||||
|
||||
❌ **WRONG:**
|
||||
|
||||
composer.json:
|
||||
```json
|
||||
"require": {
|
||||
"typo3/cms-core": "^13.4"
|
||||
}
|
||||
```
|
||||
|
||||
ext_emconf.php:
|
||||
```php
|
||||
'typo3' => '12.4.0-12.4.99', // Mismatch!
|
||||
```
|
||||
|
||||
✅ **CORRECT:**
|
||||
|
||||
composer.json:
|
||||
```json
|
||||
"require": {
|
||||
"typo3/cms-core": "^13.4"
|
||||
}
|
||||
```
|
||||
|
||||
ext_emconf.php:
|
||||
```php
|
||||
'typo3' => '13.4.0-13.4.99', // Matches!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Critical Checks
|
||||
```bash
|
||||
# Will TER upload fail?
|
||||
grep "declare(strict_types" ext_emconf.php && echo "❌ TER FAIL"
|
||||
|
||||
# Uses $_EXTKEY?
|
||||
grep '\$EM_CONF\[$_EXTKEY\]' ext_emconf.php && echo "✅ OK"
|
||||
|
||||
# Valid category?
|
||||
grep -oP "'category' => '\K[a-z]+(?=')" ext_emconf.php | grep -qE '^(be|module|fe|plugin|misc|services|templates|example|doc|distribution)$' && echo "✅ OK"
|
||||
|
||||
# Valid state?
|
||||
grep -oP "'state' => '\K[a-z]+(?=')" ext_emconf.php | grep -qE '^(alpha|beta|stable|experimental|test|obsolete|excludeFromUpdates)$' && echo "✅ OK"
|
||||
```
|
||||
413
skills/typo3-conformance/references/ext-files-validation.md
Normal file
413
skills/typo3-conformance/references/ext-files-validation.md
Normal file
@@ -0,0 +1,413 @@
|
||||
# Extension Files Validation Standards (TYPO3 v13)
|
||||
|
||||
**Sources:** TYPO3 Core API Reference v13.4
|
||||
**Purpose:** Validation rules for ext_localconf.php, ext_tables.php, ext_tables.sql, ext_tables_static+adt.sql, ext_conf_template.txt
|
||||
|
||||
## ext_localconf.php
|
||||
|
||||
### Purpose
|
||||
Global configuration file loaded during TYPO3 bootstrap in frontend, backend, and CLI contexts.
|
||||
|
||||
### Required Structure
|
||||
```php
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
defined('TYPO3') or die();
|
||||
|
||||
// Configuration code here
|
||||
```
|
||||
|
||||
### What SHOULD Be Included
|
||||
✅ Registering hooks, XCLASSes, array assignments to `$GLOBALS['TYPO3_CONF_VARS']`
|
||||
✅ Registering Request Handlers
|
||||
✅ Adding default TypoScript via ExtensionManagementUtility APIs
|
||||
✅ Registering Scheduler Tasks
|
||||
✅ Adding reports to reports module
|
||||
✅ Registering Services via Service API
|
||||
|
||||
### What Should NOT Be Included
|
||||
❌ Function and class definitions (use services/utility classes)
|
||||
❌ Class loader or package manager configuration
|
||||
❌ Cache/config manager settings
|
||||
❌ Log manager configuration
|
||||
❌ Time zone, memory limit, locale settings
|
||||
❌ Icon registration (use `Icons.php` instead)
|
||||
|
||||
### TYPO3 v13 Deprecations
|
||||
|
||||
**❌ DEPRECATED:** `\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addUserTSConfig()`
|
||||
- **Removal:** TYPO3 v14.0
|
||||
- **Alternative:** Use `Configuration/user.tsconfig` file instead
|
||||
|
||||
**❌ DEPRECATED (since v12):** Page TSconfig in ext_localconf.php
|
||||
- **Alternative:** Use `Configuration/page.tsconfig` file instead
|
||||
|
||||
### Validation Commands
|
||||
```bash
|
||||
# Check required structure
|
||||
head -5 ext_localconf.php | grep "declare(strict_types=1)" && echo "✅ Has strict_types"
|
||||
head -5 ext_localconf.php | grep "defined('TYPO3')" && echo "✅ Has TYPO3 guard"
|
||||
|
||||
# Check for deprecated addUserTSConfig
|
||||
grep "addUserTSConfig" ext_localconf.php && echo "⚠️ DEPRECATED: Use Configuration/user.tsconfig"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ext_tables.php
|
||||
|
||||
### Deprecation Status
|
||||
**PHASING OUT:** Increasingly replaced by modern configuration approaches.
|
||||
|
||||
### What Should NOT Be in ext_tables.php (v13)
|
||||
|
||||
❌ **TCA configurations** → Use `Configuration/TCA/tablename.php`
|
||||
❌ **TCA overrides** → Use `Configuration/TCA/Overrides/somefile.php`
|
||||
❌ **Insert records** → Move to TCA Overrides files
|
||||
❌ **Static files** → Move to `Configuration/TCA/Overrides/sys_template.php`
|
||||
❌ **Backend modules** → Moved to `Configuration/Backend/` in v13.0
|
||||
|
||||
### Appropriate Uses (Remaining)
|
||||
✅ Registering scheduler tasks with localization labels
|
||||
✅ Registering custom page types
|
||||
✅ Extending backend user settings
|
||||
|
||||
### v13 Migration
|
||||
|
||||
**Backend Module Registration:**
|
||||
```php
|
||||
❌ OLD (ext_tables.php):
|
||||
ExtensionUtility::registerModule(...);
|
||||
|
||||
✅ NEW (Configuration/Backend/Modules.php):
|
||||
return [
|
||||
'web_myext' => [
|
||||
'parent' => 'web',
|
||||
'position' => ['after' => 'web_list'],
|
||||
// ...
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
### Validation Commands
|
||||
```bash
|
||||
# Check for TCA modifications (should be in TCA/Overrides/)
|
||||
grep -E "addTCAcolumns|addToAllTCAtypes" ext_tables.php && echo "⚠️ WARNING: Move to TCA/Overrides/"
|
||||
|
||||
# Check for backend module registration (should be in Configuration/Backend/)
|
||||
grep "registerModule" ext_tables.php && echo "⚠️ WARNING: Move to Configuration/Backend/Modules.php"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ext_tables.sql
|
||||
|
||||
### Purpose
|
||||
Defines database tables and columns for extensions. Parsed when extensions are enabled.
|
||||
|
||||
### SQL Syntax Requirements
|
||||
|
||||
**Format:** Follow `mysqldump` utility output style
|
||||
- TYPO3 parses and converts to target DBMS (MySQL, MariaDB, PostgreSQL, SQLite)
|
||||
- Partial definitions allowed when extending existing tables
|
||||
|
||||
### Table Naming Conventions
|
||||
```sql
|
||||
-- Extension tables with prefix
|
||||
CREATE TABLE tx_myextension_domain_model_table (
|
||||
field_name varchar(255) DEFAULT '' NOT NULL,
|
||||
);
|
||||
|
||||
-- Extending core tables
|
||||
CREATE TABLE pages (
|
||||
tx_myextension_field int(11) DEFAULT '0' NOT NULL,
|
||||
);
|
||||
```
|
||||
|
||||
### Auto-Generated Columns
|
||||
If TCA exists, TYPO3 automatically creates:
|
||||
- `uid` with PRIMARY KEY
|
||||
- `pid` (unsigned) with default index `parent`
|
||||
- System fields based on TCA `ctrl` properties
|
||||
|
||||
### New in v13: Empty Table Definitions
|
||||
```sql
|
||||
-- Valid when TCA enriches fields
|
||||
CREATE TABLE tx_myextension_table (
|
||||
);
|
||||
```
|
||||
|
||||
### v13.4 CHAR/BINARY Handling
|
||||
|
||||
**WARNING:** Fixed-length types now properly flagged
|
||||
- Use only with ensured fixed-length values (hash identifiers)
|
||||
- **Avoid with Extbase ORM** (cannot ensure fixed-length in queries)
|
||||
- Test extensively across database platforms
|
||||
|
||||
**Best Practice:**
|
||||
```sql
|
||||
✅ VARCHAR(255) -- Variable length (preferred)
|
||||
⚠️ CHAR(32) -- Fixed length (use cautiously)
|
||||
✅ VARBINARY(255) -- Variable binary (preferred)
|
||||
⚠️ BINARY(16) -- Fixed binary (use cautiously)
|
||||
```
|
||||
|
||||
### Validation Commands
|
||||
```bash
|
||||
# Check table naming
|
||||
grep "CREATE TABLE" ext_tables.sql | grep -E "tx_[a-z_]+" && echo "✅ Proper naming"
|
||||
|
||||
# Check for CHAR usage (potential issue)
|
||||
grep -E "CHAR\([0-9]+\)" ext_tables.sql && echo "⚠️ WARNING: CHAR type found - verify fixed-length"
|
||||
|
||||
# Validate syntax
|
||||
php -r "file_get_contents('ext_tables.sql');" && echo "✅ File readable"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ext_tables_static+adt.sql
|
||||
|
||||
### Purpose
|
||||
Stores static SQL INSERT statements for pre-populated data.
|
||||
|
||||
### Critical Restrictions
|
||||
|
||||
**❌ ONLY INSERT statements allowed**
|
||||
- No CREATE TABLE
|
||||
- No ALTER TABLE
|
||||
- No UPDATE/DELETE
|
||||
|
||||
**⚠️ Warning:** "Static data is not meant to be extended by other extensions. On re-import all extended fields and data is lost."
|
||||
|
||||
### When to Use
|
||||
- Initial data required during installation
|
||||
- Lookup tables, predefined categories
|
||||
- Default configuration data
|
||||
|
||||
### Re-import Behavior
|
||||
- Data truncated and reimported when file contents change
|
||||
- Executed via:
|
||||
- `bin/typo3 extension:setup`
|
||||
- Admin Tools > Extensions reload
|
||||
|
||||
### Generation Command
|
||||
```bash
|
||||
mysqldump --user=[user] --password [database] [tablename] > ./ext_tables_static+adt.sql
|
||||
```
|
||||
|
||||
### Validation Commands
|
||||
```bash
|
||||
# Check file exists
|
||||
[ -f "ext_tables_static+adt.sql" ] && echo "✅ Static data file present"
|
||||
|
||||
# Verify only INSERT statements
|
||||
grep -v "^INSERT" ext_tables_static+adt.sql | grep -E "^(CREATE|ALTER|UPDATE|DELETE)" && echo "❌ CRITICAL: Only INSERT allowed"
|
||||
|
||||
# Check corresponding table definition exists
|
||||
grep "CREATE TABLE" ext_tables.sql && echo "✅ Table definitions present"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ext_conf_template.txt
|
||||
|
||||
### Purpose
|
||||
Defines extension configuration options in Admin Tools > Settings module.
|
||||
|
||||
### Syntax Format
|
||||
```
|
||||
# cat=Category; type=fieldtype; label=LLL:EXT:key/path.xlf:label
|
||||
optionName = defaultValue
|
||||
```
|
||||
|
||||
### Field Types
|
||||
|
||||
| Type | Purpose | Example |
|
||||
|------|---------|---------
|
||||
| `boolean` | Checkbox | `type=boolean` |
|
||||
| `string` | Text field | `type=string` |
|
||||
| `int` / `integer` | Whole number | `type=int` |
|
||||
| `int+` | Positive integers | `type=int+` |
|
||||
| `color` | Color picker | `type=color` |
|
||||
| `options` | Select dropdown | `type=options[Val1=1,Val2=2]` |
|
||||
| `user` | Custom function | `type=user[Vendor\Class->method]` |
|
||||
| `small` | Compact text field | `type=small` |
|
||||
| `wrap` | Wrapper field | `type=wrap` |
|
||||
| `offset` | Offset value | `type=offset` |
|
||||
|
||||
### Options Syntax
|
||||
```
|
||||
# cat=basic; type=options[Option 1=value1,Option 2=value2]; label=Select Option
|
||||
variable = value1
|
||||
```
|
||||
|
||||
### User Function Syntax
|
||||
```
|
||||
# cat=advanced; type=user[Vendor\Extension\Class->methodName]; label=Custom Field
|
||||
variable = 1
|
||||
```
|
||||
|
||||
### Nested Structure
|
||||
```
|
||||
directories {
|
||||
# cat=paths; type=string; label=Temp directory
|
||||
tmp = /tmp
|
||||
|
||||
# cat=paths; type=string; label=Upload directory
|
||||
uploads = /uploads
|
||||
}
|
||||
```
|
||||
|
||||
**Access:** `$config['directories']['tmp']`
|
||||
|
||||
### Localization
|
||||
```
|
||||
# Use LLL references for multi-language support
|
||||
# cat=basic; type=string; label=LLL:EXT:my_ext/Resources/Private/Language/locallang.xlf:config.title
|
||||
title = Default Title
|
||||
```
|
||||
|
||||
### Validation Commands
|
||||
```bash
|
||||
# Check file exists
|
||||
[ -f "ext_conf_template.txt" ] && echo "✅ Configuration template present"
|
||||
|
||||
# Check syntax format
|
||||
grep -E "^#.*cat=.*type=.*label=" ext_conf_template.txt && echo "✅ Valid syntax found"
|
||||
|
||||
# Check for localization
|
||||
grep "LLL:EXT:" ext_conf_template.txt && echo "✅ Uses localized labels"
|
||||
|
||||
# Validate field types
|
||||
grep -E "type=(boolean|string|int|int\+|color|options|user|small|wrap|offset)" ext_conf_template.txt && echo "✅ Valid field types"
|
||||
```
|
||||
|
||||
### Accessing Configuration in Code
|
||||
```php
|
||||
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
|
||||
|
||||
public function __construct(
|
||||
private readonly ExtensionConfiguration $extensionConfiguration
|
||||
) {}
|
||||
|
||||
// Get all configuration
|
||||
$config = $this->extensionConfiguration->get('extension_key');
|
||||
|
||||
// Get specific value
|
||||
$value = $this->extensionConfiguration->get('extension_key', 'optionName');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
### ext_localconf.php
|
||||
- [ ] Has `declare(strict_types=1)` at top
|
||||
- [ ] Has `defined('TYPO3') or die();` guard
|
||||
- [ ] No function/class definitions
|
||||
- [ ] **NOT** using deprecated `addUserTSConfig()`
|
||||
- [ ] **NOT** adding page TSconfig (use Configuration/page.tsconfig)
|
||||
|
||||
### ext_tables.php
|
||||
- [ ] No TCA definitions (use Configuration/TCA/)
|
||||
- [ ] No TCA overrides (use Configuration/TCA/Overrides/)
|
||||
- [ ] No backend module registration (use Configuration/Backend/)
|
||||
- [ ] Only contains appropriate v13 use cases
|
||||
|
||||
### ext_tables.sql
|
||||
- [ ] Follows mysqldump syntax
|
||||
- [ ] Tables prefixed with `tx_<extensionkey>_`
|
||||
- [ ] Uses VARCHAR/VARBINARY (not CHAR/BINARY unless necessary)
|
||||
- [ ] Empty table definitions if TCA provides fields
|
||||
|
||||
### ext_tables_static+adt.sql (if present)
|
||||
- [ ] **ONLY** INSERT statements (no CREATE/ALTER)
|
||||
- [ ] Corresponding table structure in ext_tables.sql
|
||||
- [ ] Static data is truly static (not extended by other extensions)
|
||||
|
||||
### ext_conf_template.txt (if present)
|
||||
- [ ] Syntax: `# cat=; type=; label=`
|
||||
- [ ] Valid field types used
|
||||
- [ ] Localized labels with LLL: references
|
||||
- [ ] Proper categorization
|
||||
- [ ] Sensible default values
|
||||
|
||||
---
|
||||
|
||||
## Common Violations and Fixes
|
||||
|
||||
### ext_localconf.php: Using Deprecated addUserTSConfig
|
||||
|
||||
❌ Before:
|
||||
```php
|
||||
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addUserTSConfig('
|
||||
options.pageTree.showPageIdWithTitle = 1
|
||||
');
|
||||
```
|
||||
|
||||
✅ After:
|
||||
```
|
||||
// Create Configuration/user.tsconfig
|
||||
options.pageTree.showPageIdWithTitle = 1
|
||||
```
|
||||
|
||||
### ext_tables.php: Backend Module in ext_tables.php
|
||||
|
||||
❌ Before (ext_tables.php):
|
||||
```php
|
||||
ExtensionUtility::registerModule('MyExt', 'web', 'mymodule', ...);
|
||||
```
|
||||
|
||||
✅ After (Configuration/Backend/Modules.php):
|
||||
```php
|
||||
return [
|
||||
'web_myext_mymodule' => [
|
||||
'parent' => 'web',
|
||||
'position' => ['after' => 'web_list'],
|
||||
'access' => 'user',
|
||||
'path' => '/module/web/myext',
|
||||
'labels' => 'LLL:EXT:my_ext/Resources/Private/Language/locallang_mod.xlf',
|
||||
'extensionName' => 'MyExt',
|
||||
'controllerActions' => [
|
||||
\Vendor\MyExt\Controller\ModuleController::class => ['list', 'detail'],
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
### ext_tables.sql: Using CHAR Inappropriately
|
||||
|
||||
❌ Before:
|
||||
```sql
|
||||
CREATE TABLE tx_myext_table (
|
||||
name CHAR(255) DEFAULT '' NOT NULL, -- Variable content!
|
||||
);
|
||||
```
|
||||
|
||||
✅ After:
|
||||
```sql
|
||||
CREATE TABLE tx_myext_table (
|
||||
name VARCHAR(255) DEFAULT '' NOT NULL, -- Use VARCHAR
|
||||
);
|
||||
```
|
||||
|
||||
### ext_tables_static+adt.sql: Including CREATE Statements
|
||||
|
||||
❌ Before:
|
||||
```sql
|
||||
CREATE TABLE tx_myext_categories (
|
||||
uid int(11) NOT NULL auto_increment,
|
||||
title varchar(255) DEFAULT '' NOT NULL,
|
||||
PRIMARY KEY (uid)
|
||||
);
|
||||
INSERT INTO tx_myext_categories VALUES (1, 'Category 1');
|
||||
```
|
||||
|
||||
✅ After:
|
||||
```sql
|
||||
-- Move CREATE to ext_tables.sql
|
||||
-- Only INSERT in ext_tables_static+adt.sql
|
||||
INSERT INTO tx_myext_categories (uid, title) VALUES (1, 'Category 1');
|
||||
INSERT INTO tx_myext_categories (uid, title) VALUES (2, 'Category 2');
|
||||
```
|
||||
273
skills/typo3-conformance/references/extension-architecture.md
Normal file
273
skills/typo3-conformance/references/extension-architecture.md
Normal file
@@ -0,0 +1,273 @@
|
||||
# TYPO3 Extension Architecture Standards
|
||||
|
||||
**Source:** TYPO3 Core API Reference - Extension Architecture
|
||||
**Purpose:** File structure, directory hierarchy, and required files for TYPO3 extensions
|
||||
|
||||
## Required Files
|
||||
|
||||
### Essential Files
|
||||
|
||||
**composer.json**
|
||||
- REQUIRED for all extensions
|
||||
- Defines package metadata, dependencies, PSR-4 autoloading
|
||||
- Example:
|
||||
```json
|
||||
{
|
||||
"name": "vendor/extension-key",
|
||||
"type": "typo3-cms-extension",
|
||||
"require": {
|
||||
"typo3/cms-core": "^12.4 || ^13.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Vendor\\ExtensionKey\\": "Classes/"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**ext_emconf.php**
|
||||
- REQUIRED for TER (TYPO3 Extension Repository) publication
|
||||
- Contains extension metadata
|
||||
- Example:
|
||||
```php
|
||||
<?php
|
||||
$EM_CONF[$_EXTKEY] = [
|
||||
'title' => 'Extension Title',
|
||||
'description' => 'Extension description',
|
||||
'category' => 'fe',
|
||||
'author' => 'Author Name',
|
||||
'author_email' => 'author@example.com',
|
||||
'state' => 'stable',
|
||||
'version' => '1.0.0',
|
||||
'constraints' => [
|
||||
'depends' => [
|
||||
'typo3' => '12.4.0-13.9.99',
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
**Documentation/Index.rst**
|
||||
- REQUIRED for docs.typo3.org publication
|
||||
- Main documentation entry point
|
||||
- Must follow reStructuredText format
|
||||
|
||||
**Documentation/Settings.cfg**
|
||||
- REQUIRED for docs.typo3.org publication
|
||||
- Contains documentation project settings
|
||||
- Example:
|
||||
```ini
|
||||
[general]
|
||||
project = Extension Name
|
||||
release = 1.0.0
|
||||
copyright = 2024
|
||||
|
||||
[html_theme_options]
|
||||
project_home = https://github.com/vendor/extension
|
||||
```
|
||||
|
||||
## Directory Structure
|
||||
|
||||
### Core Directories
|
||||
|
||||
**Classes/**
|
||||
- Contains all PHP classes
|
||||
- MUST follow PSR-4 autoloading structure
|
||||
- Namespace: `\VendorName\ExtensionKey\`
|
||||
- Common subdirectories:
|
||||
- `Classes/Controller/` - Extbase/backend controllers
|
||||
- `Classes/Domain/Model/` - Domain models
|
||||
- `Classes/Domain/Repository/` - Repositories
|
||||
- `Classes/Service/` - Service classes
|
||||
- `Classes/Utility/` - Utility classes
|
||||
- `Classes/ViewHelper/` - Fluid ViewHelpers
|
||||
- `Classes/EventListener/` - PSR-14 event listeners
|
||||
|
||||
**Configuration/**
|
||||
- Contains all configuration files
|
||||
- Required subdirectories:
|
||||
- `Configuration/TCA/` - Table Configuration Array definitions
|
||||
- `Configuration/Backend/` - Backend module configuration
|
||||
- `Configuration/TypoScript/` - TypoScript configuration
|
||||
- `Configuration/Sets/` - Configuration sets (TYPO3 v13+)
|
||||
- Optional files:
|
||||
- `Configuration/Services.yaml` - Dependency injection configuration
|
||||
- `Configuration/TsConfig/` - Page/User TSconfig
|
||||
- `Configuration/RequestMiddlewares.php` - PSR-15 middlewares
|
||||
|
||||
**Resources/**
|
||||
- Contains all frontend/backend resources
|
||||
- Structure:
|
||||
- `Resources/Private/` - Non-public files
|
||||
- `Resources/Private/Templates/` - Fluid templates
|
||||
- `Resources/Private/Partials/` - Fluid partials
|
||||
- `Resources/Private/Layouts/` - Fluid layouts
|
||||
- `Resources/Private/Language/` - Translation files (XLIFF)
|
||||
- `Resources/Public/` - Publicly accessible files
|
||||
- `Resources/Public/Css/` - Stylesheets
|
||||
- `Resources/Public/JavaScript/` - JavaScript files
|
||||
- `Resources/Public/Icons/` - Extension icons
|
||||
- `Resources/Public/Images/` - Images
|
||||
|
||||
**Tests/**
|
||||
- Contains all test files
|
||||
- Structure:
|
||||
- `Tests/Unit/` - PHPUnit unit tests
|
||||
- `Tests/Functional/` - PHPUnit functional tests
|
||||
- `Tests/Acceptance/` - Codeception acceptance tests
|
||||
- MUST mirror `Classes/` structure
|
||||
|
||||
**Documentation/**
|
||||
- Contains RST documentation
|
||||
- MUST include `Index.rst` and `Settings.cfg`
|
||||
- Common structure:
|
||||
- `Documentation/Introduction/`
|
||||
- `Documentation/Installation/`
|
||||
- `Documentation/Configuration/`
|
||||
- `Documentation/Developer/`
|
||||
- `Documentation/Editor/`
|
||||
|
||||
## Reserved File Prefixes
|
||||
|
||||
Files with the `ext_*` prefix are reserved for special purposes:
|
||||
|
||||
**ext_emconf.php**
|
||||
- Extension metadata (REQUIRED for TER)
|
||||
|
||||
**ext_localconf.php**
|
||||
- Global configuration executed in both frontend and backend
|
||||
- Register hooks, event listeners, XCLASSes
|
||||
- Add plugins, content elements
|
||||
- Register services
|
||||
|
||||
**ext_tables.php**
|
||||
- Backend-specific configuration
|
||||
- Register backend modules
|
||||
- Add TCA modifications
|
||||
- DEPRECATED in favor of dedicated configuration files
|
||||
|
||||
**ext_tables.sql**
|
||||
- Database table definitions
|
||||
- Executed during extension installation
|
||||
- Contains CREATE TABLE and ALTER TABLE statements
|
||||
|
||||
**ext_conf_template.txt**
|
||||
- Extension configuration template
|
||||
- Defines settings available in Extension Configuration
|
||||
- TypoScript-like syntax
|
||||
|
||||
## File Naming Conventions
|
||||
|
||||
### PHP Classes
|
||||
- File name MUST match class name exactly
|
||||
- PSR-4 compliant
|
||||
- Example: `Classes/Controller/MyController.php` → `class MyController`
|
||||
|
||||
### Database Tables
|
||||
- Pattern: `tx_<extensionkeyprefix>_<tablename>`
|
||||
- Example: `tx_myext_domain_model_product`
|
||||
- Extension key must be converted to lowercase, underscores allowed
|
||||
|
||||
### TCA Files
|
||||
- Pattern: `Configuration/TCA/<tablename>.php`
|
||||
- Returns TCA array
|
||||
- Example: `Configuration/TCA/tx_myext_domain_model_product.php`
|
||||
|
||||
### Language Files
|
||||
- Pattern: `Resources/Private/Language/<context>.xlf`
|
||||
- XLIFF 1.2 format
|
||||
- Example: `Resources/Private/Language/locallang.xlf`
|
||||
|
||||
## Architecture Best Practices
|
||||
|
||||
### PSR-4 Autoloading
|
||||
- All classes in `Classes/` directory
|
||||
- Namespace structure MUST match directory structure
|
||||
- Example:
|
||||
- Class: `Vendor\ExtensionKey\Domain\Model\Product`
|
||||
- File: `Classes/Domain/Model/Product.php`
|
||||
|
||||
### Dependency Injection
|
||||
- Use constructor injection for dependencies
|
||||
- Register services in `Configuration/Services.yaml`
|
||||
- Example:
|
||||
```yaml
|
||||
services:
|
||||
_defaults:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
public: false
|
||||
|
||||
Vendor\ExtensionKey\:
|
||||
resource: '../Classes/*'
|
||||
```
|
||||
|
||||
### Configuration Files
|
||||
- Separate concerns into dedicated configuration files
|
||||
- Use `Configuration/Backend/` for backend modules (not ext_tables.php)
|
||||
- Use `Configuration/TCA/` for table definitions
|
||||
- Use `Configuration/TypoScript/` for TypoScript
|
||||
|
||||
### Testing Structure
|
||||
- Mirror `Classes/` structure in `Tests/Unit/` and `Tests/Functional/`
|
||||
- Example:
|
||||
- Class: `Classes/Service/CalculationService.php`
|
||||
- Unit Test: `Tests/Unit/Service/CalculationServiceTest.php`
|
||||
- Functional Test: `Tests/Functional/Service/CalculationServiceTest.php`
|
||||
|
||||
## Common Issues
|
||||
|
||||
### ❌ Wrong: Mixed file types in root
|
||||
```
|
||||
my_extension/
|
||||
├── MyController.php # WRONG: PHP in root
|
||||
├── config.yaml # WRONG: Config in root
|
||||
└── styles.css # WRONG: CSS in root
|
||||
```
|
||||
|
||||
### ✅ Right: Proper directory structure
|
||||
```
|
||||
my_extension/
|
||||
├── Classes/Controller/MyController.php
|
||||
├── Configuration/Services.yaml
|
||||
└── Resources/Public/Css/styles.css
|
||||
```
|
||||
|
||||
### ❌ Wrong: Non-standard directory names
|
||||
```
|
||||
Classes/
|
||||
├── Controllers/ # WRONG: Plural
|
||||
├── Services/ # WRONG: Should be Service
|
||||
└── Helpers/ # WRONG: Use Utility
|
||||
```
|
||||
|
||||
### ✅ Right: Standard TYPO3 directory names
|
||||
```
|
||||
Classes/
|
||||
├── Controller/ # Singular
|
||||
├── Service/ # Singular
|
||||
└── Utility/ # Standard naming
|
||||
```
|
||||
|
||||
## Extension Key Naming
|
||||
|
||||
- Lowercase letters and underscores only
|
||||
- Must start with a letter
|
||||
- 3-30 characters
|
||||
- Cannot start with `tx_`, `user_`, `pages`, `tt_`, `sys_`
|
||||
- Example: `my_extension`, `blog_example`, `news`
|
||||
|
||||
## Conformance Checklist
|
||||
|
||||
- [ ] composer.json present with correct structure
|
||||
- [ ] ext_emconf.php present with complete metadata
|
||||
- [ ] Documentation/Index.rst and Documentation/Settings.cfg present
|
||||
- [ ] Classes/ directory follows PSR-4 structure
|
||||
- [ ] Configuration/ subdirectories properly organized
|
||||
- [ ] Resources/ separated into Private/ and Public/
|
||||
- [ ] Tests/ mirror Classes/ structure
|
||||
- [ ] No PHP files in extension root (except ext_* files)
|
||||
- [ ] File naming follows conventions
|
||||
- [ ] Database table names use tx_<extensionkey>_ prefix
|
||||
- [ ] Extension key follows naming rules
|
||||
297
skills/typo3-conformance/references/hooks-and-events.md
Normal file
297
skills/typo3-conformance/references/hooks-and-events.md
Normal file
@@ -0,0 +1,297 @@
|
||||
# TYPO3 Hooks and PSR-14 Events
|
||||
|
||||
**Source:** TYPO3 Core API Reference - Hooks, Events, and Signals
|
||||
**Purpose:** Understanding TYPO3 hook system, PSR-14 events, and migration strategies
|
||||
|
||||
## SC_OPTIONS Hooks Status in TYPO3 13
|
||||
|
||||
### ⚠️ Common Misconception
|
||||
|
||||
**INCORRECT:** "SC_OPTIONS hooks are deprecated in TYPO3 13"
|
||||
|
||||
**CORRECT:** SC_OPTIONS hooks are **NOT deprecated** in TYPO3 13. They remain the **official pattern** for specific use cases.
|
||||
|
||||
### SC_OPTIONS Hooks That Are Still Official
|
||||
|
||||
The following SC_OPTIONS hooks remain the official TYPO3 13 pattern:
|
||||
|
||||
#### DataHandler Hooks (Still Official)
|
||||
|
||||
```php
|
||||
// Configuration/Services.yaml
|
||||
Vendor\Extension\Database\MyDataHandlerHook:
|
||||
public: true
|
||||
tags:
|
||||
- name: event.listener
|
||||
identifier: 'my-extension/datahandler-hook'
|
||||
method: 'processDatamap_postProcessFieldArray'
|
||||
```
|
||||
|
||||
**Still Official in ext_localconf.php:**
|
||||
```php
|
||||
<?php
|
||||
// TYPO3 13+ DataHandler hooks remain official pattern
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][] =
|
||||
\Vendor\Extension\Database\MyDataHandlerHook::class;
|
||||
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass'][] =
|
||||
\Vendor\Extension\Database\MyDataHandlerHook::class;
|
||||
```
|
||||
|
||||
**Key DataHandler Hook Methods (TYPO3 13+):**
|
||||
- `processDatamap_preProcessFieldArray()` - Before field processing
|
||||
- `processDatamap_postProcessFieldArray()` - After field processing
|
||||
- `processDatamap_afterDatabaseOperations()` - After DB operations
|
||||
- `processCmdmap_preProcess()` - Before command processing
|
||||
- `processCmdmap_postProcess()` - After command processing
|
||||
- `processCmdmap_afterFinish()` - After all commands finished
|
||||
|
||||
#### RTE Transformation Hooks (Still Official)
|
||||
|
||||
```php
|
||||
<?php
|
||||
// TYPO3 13+ RTE transformation hooks remain official pattern
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_parsehtml_proc.php']['transformation'][] =
|
||||
\Vendor\Extension\RteTransformation\MyRteTransformationHook::class;
|
||||
```
|
||||
|
||||
**Required Methods:**
|
||||
- `transform_rte()` - Transform content from database to RTE
|
||||
- `transform_db()` - Transform content from RTE to database
|
||||
|
||||
### When to Use SC_OPTIONS vs PSR-14 Events
|
||||
|
||||
| Scenario | Use SC_OPTIONS Hook | Use PSR-14 Event |
|
||||
|----------|-------------------|------------------|
|
||||
| DataHandler field processing | ✅ Yes (official) | ❌ No event available |
|
||||
| RTE content transformation | ✅ Yes (official) | ❌ No event available |
|
||||
| Backend user authentication | ❌ Migrated | ✅ Use PSR-14 events |
|
||||
| Frontend rendering | ❌ Migrated | ✅ Use PSR-14 events |
|
||||
| Page generation | ❌ Migrated | ✅ Use PSR-14 events |
|
||||
| Cache clearing | ❌ Migrated | ✅ Use PSR-14 events |
|
||||
|
||||
## PSR-14 Event Listeners (Preferred)
|
||||
|
||||
For most scenarios, PSR-14 events are the modern TYPO3 13+ approach.
|
||||
|
||||
### Event Listener Configuration
|
||||
|
||||
```yaml
|
||||
# Configuration/Services.yaml
|
||||
services:
|
||||
_defaults:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
public: false
|
||||
|
||||
Vendor\Extension\:
|
||||
resource: '../Classes/*'
|
||||
|
||||
# PSR-14 Event Listener
|
||||
Vendor\Extension\EventListener\MyEventListener:
|
||||
tags:
|
||||
- name: event.listener
|
||||
identifier: 'my-extension/my-event-listener'
|
||||
event: TYPO3\CMS\Core\Authentication\Event\AfterUserLoggedInEvent
|
||||
```
|
||||
|
||||
### Event Listener Implementation
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Vendor\Extension\EventListener;
|
||||
|
||||
use TYPO3\CMS\Core\Authentication\Event\AfterUserLoggedInEvent;
|
||||
|
||||
/**
|
||||
* PSR-14 Event Listener for user login.
|
||||
*/
|
||||
final class MyEventListener
|
||||
{
|
||||
public function __invoke(AfterUserLoggedInEvent $event): void
|
||||
{
|
||||
$user = $event->getUser();
|
||||
|
||||
// Your logic here
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Common TYPO3 13 Events
|
||||
|
||||
**Authentication Events:**
|
||||
- `AfterUserLoggedInEvent`
|
||||
- `BeforeUserLogoutEvent`
|
||||
- `AfterUserLoggedOutEvent`
|
||||
|
||||
**Backend Events:**
|
||||
- `ModifyButtonBarEvent`
|
||||
- `ModifyDatabaseQueryForContentEvent`
|
||||
- `BeforePagePreviewUriGeneratedEvent`
|
||||
|
||||
**DataHandler Events:**
|
||||
- `AfterDataInsertedEvent`
|
||||
- `AfterDataUpdatedEvent`
|
||||
- `AfterRecordDeletedEvent`
|
||||
|
||||
**Page Events:**
|
||||
- `AfterPageTreeItemsPreparedEvent`
|
||||
- `ModifyPageLayoutContentEvent`
|
||||
|
||||
**Complete Reference:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Events/EventDispatcher/Index.html
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
### Step 1: Identify Hook Type
|
||||
|
||||
```php
|
||||
// Check if hook is in ext_localconf.php
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['...']['...'][]
|
||||
```
|
||||
|
||||
### Step 2: Check Official Documentation
|
||||
|
||||
- **DataHandler hooks:** Still official, keep using SC_OPTIONS
|
||||
- **RTE transformation:** Still official, keep using SC_OPTIONS
|
||||
- **Other hooks:** Check if PSR-14 event exists
|
||||
|
||||
### Step 3: Migrate or Modernize
|
||||
|
||||
**If PSR-14 event exists:**
|
||||
```php
|
||||
// OLD: ext_localconf.php
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postUserLookUp'][]
|
||||
= \Vendor\Extension\Hook\MyHook::class;
|
||||
|
||||
// NEW: Configuration/Services.yaml + EventListener class
|
||||
Vendor\Extension\EventListener\MyEventListener:
|
||||
tags:
|
||||
- name: event.listener
|
||||
identifier: 'my-extension/after-login'
|
||||
event: TYPO3\CMS\Core\Authentication\Event\AfterUserLoggedInEvent
|
||||
```
|
||||
|
||||
**If no PSR-14 event exists (DataHandler, RTE):**
|
||||
```php
|
||||
// KEEP: Still official in TYPO3 13+
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][]
|
||||
= \Vendor\Extension\Database\MyDataHandlerHook::class;
|
||||
|
||||
// MODERNIZE: Add dependency injection
|
||||
// Configuration/Services.yaml
|
||||
Vendor\Extension\Database\MyDataHandlerHook:
|
||||
public: true
|
||||
arguments:
|
||||
$resourceFactory: '@TYPO3\CMS\Core\Resource\ResourceFactory'
|
||||
$context: '@TYPO3\CMS\Core\Context\Context'
|
||||
$logManager: '@TYPO3\CMS\Core\Log\LogManager'
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Constructor Dependency Injection
|
||||
|
||||
Even for SC_OPTIONS hooks, use constructor injection (TYPO3 13+):
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Vendor\Extension\Database;
|
||||
|
||||
use TYPO3\CMS\Core\Context\Context;
|
||||
use TYPO3\CMS\Core\Log\LogManager;
|
||||
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||
|
||||
/**
|
||||
* DataHandler hook with dependency injection.
|
||||
*/
|
||||
final class MyDataHandlerHook
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ResourceFactory $resourceFactory,
|
||||
private readonly Context $context,
|
||||
private readonly LogManager $logManager,
|
||||
) {}
|
||||
|
||||
public function processDatamap_postProcessFieldArray(
|
||||
string $status,
|
||||
string $table,
|
||||
string $id,
|
||||
array &$fieldArray,
|
||||
\TYPO3\CMS\Core\DataHandling\DataHandler &$dataHandler,
|
||||
): void {
|
||||
// Use injected dependencies
|
||||
$file = $this->resourceFactory->getFileObject($fileId);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Avoid GeneralUtility::makeInstance()
|
||||
|
||||
```php
|
||||
// ❌ BAD: Using makeInstance (legacy pattern)
|
||||
$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
|
||||
|
||||
// ✅ GOOD: Constructor injection (TYPO3 13+ pattern)
|
||||
public function __construct(
|
||||
private readonly ResourceFactory $resourceFactory,
|
||||
) {}
|
||||
```
|
||||
|
||||
### 3. Configure Services Explicitly
|
||||
|
||||
```yaml
|
||||
# Configuration/Services.yaml
|
||||
services:
|
||||
Vendor\Extension\Database\MyDataHandlerHook:
|
||||
public: true # Required for SC_OPTIONS hooks
|
||||
arguments:
|
||||
$resourceFactory: '@TYPO3\CMS\Core\Resource\ResourceFactory'
|
||||
$context: '@TYPO3\CMS\Core\Context\Context'
|
||||
$logManager: '@TYPO3\CMS\Core\Log\LogManager'
|
||||
```
|
||||
|
||||
## Acceptable $GLOBALS Usage
|
||||
|
||||
Even in TYPO3 13+, certain `$GLOBALS` usage is acceptable:
|
||||
|
||||
### ✅ Acceptable $GLOBALS
|
||||
|
||||
```php
|
||||
// TCA access (no alternative available)
|
||||
$GLOBALS['TCA']['tt_content']['columns']['bodytext']
|
||||
|
||||
// Current request (framework-provided)
|
||||
$GLOBALS['TYPO3_REQUEST']
|
||||
|
||||
// Backend user context (framework-provided)
|
||||
$GLOBALS['BE_USER']
|
||||
|
||||
// Frontend user context (framework-provided)
|
||||
$GLOBALS['TSFE']
|
||||
```
|
||||
|
||||
### ❌ Avoid $GLOBALS
|
||||
|
||||
```php
|
||||
// Database connection (use ConnectionPool)
|
||||
$GLOBALS['TYPO3_DB']
|
||||
|
||||
// Extension configuration (use ExtensionConfiguration)
|
||||
$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['my_ext']
|
||||
|
||||
// Object instantiation (use dependency injection)
|
||||
GeneralUtility::makeInstance(SomeClass::class)
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [TYPO3 Hooks Documentation](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Hooks/Index.html)
|
||||
- [PSR-14 Events](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Events/EventDispatcher/Index.html)
|
||||
- [DataHandler Hooks](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Hooks/DataHandler/Index.html)
|
||||
- [Dependency Injection](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/DependencyInjection/Index.html)
|
||||
918
skills/typo3-conformance/references/php-architecture.md
Normal file
918
skills/typo3-conformance/references/php-architecture.md
Normal file
@@ -0,0 +1,918 @@
|
||||
# TYPO3 PHP Architecture Standards
|
||||
|
||||
**Source:** TYPO3 Core API Reference - PHP Architecture
|
||||
**Purpose:** Dependency injection, services, events, Extbase, middleware patterns
|
||||
|
||||
## Dependency Injection
|
||||
|
||||
TYPO3 uses **Symfony's Dependency Injection Container** for service management.
|
||||
|
||||
### Constructor Injection (Preferred)
|
||||
|
||||
```php
|
||||
// ✅ Right: Constructor injection with readonly properties
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Vendor\ExtensionKey\Controller;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
|
||||
use Vendor\ExtensionKey\Domain\Repository\UserRepository;
|
||||
|
||||
final class UserController extends ActionController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly UserRepository $userRepository
|
||||
) {}
|
||||
|
||||
public function listAction(): ResponseInterface
|
||||
{
|
||||
$users = $this->userRepository->findAll();
|
||||
$this->view->assign('users', $users);
|
||||
return $this->htmlResponse();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Method Injection (inject* Methods)
|
||||
|
||||
```php
|
||||
// ✅ Right: Method injection for abstract classes
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Vendor\ExtensionKey\Controller;
|
||||
|
||||
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
|
||||
use Vendor\ExtensionKey\Domain\Repository\UserRepository;
|
||||
|
||||
class UserController extends ActionController
|
||||
{
|
||||
protected ?UserRepository $userRepository = null;
|
||||
|
||||
public function injectUserRepository(UserRepository $userRepository): void
|
||||
{
|
||||
$this->userRepository = $userRepository;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**When to Use Method Injection:**
|
||||
- Extending abstract core classes (ActionController, AbstractValidator)
|
||||
- Avoiding breaking changes when base class constructor changes
|
||||
- Optional dependencies
|
||||
|
||||
**When to Use Constructor Injection:**
|
||||
- All new code (preferred)
|
||||
- Required dependencies
|
||||
- Better testability
|
||||
|
||||
### Interface Injection
|
||||
|
||||
```php
|
||||
// ✅ Right: Depend on interfaces, not implementations
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Vendor\ExtensionKey\Controller;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
|
||||
use Vendor\ExtensionKey\Domain\Repository\UserRepositoryInterface;
|
||||
|
||||
final class UserController extends ActionController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly UserRepositoryInterface $userRepository
|
||||
) {}
|
||||
}
|
||||
```
|
||||
|
||||
## Service Configuration
|
||||
|
||||
### Configuration/Services.yaml
|
||||
|
||||
```yaml
|
||||
# ✅ Right: Proper service configuration
|
||||
services:
|
||||
_defaults:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
public: false
|
||||
|
||||
# Auto-register all classes
|
||||
Vendor\ExtensionKey\:
|
||||
resource: '../Classes/*'
|
||||
|
||||
# Explicit service configuration
|
||||
Vendor\ExtensionKey\Service\MyService:
|
||||
arguments:
|
||||
$configValue: '%env(MY_CONFIG_VALUE)%'
|
||||
|
||||
# Factory pattern for Connection
|
||||
Vendor\ExtensionKey\Domain\Repository\MyTableRepository:
|
||||
factory: ['@TYPO3\CMS\Core\Database\ConnectionPool', 'getConnectionForTable']
|
||||
arguments:
|
||||
- 'my_table'
|
||||
|
||||
# Interface binding
|
||||
Vendor\ExtensionKey\Domain\Repository\UserRepositoryInterface:
|
||||
class: Vendor\ExtensionKey\Domain\Repository\UserRepository
|
||||
```
|
||||
|
||||
### Autowire Attribute (TYPO3 v12+)
|
||||
|
||||
```php
|
||||
// ✅ Right: Inject configuration using Autowire attribute
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Vendor\ExtensionKey\Service;
|
||||
|
||||
use TYPO3\CMS\Core\DependencyInjection\Attribute\Autowire;
|
||||
|
||||
final class MyService
|
||||
{
|
||||
public function __construct(
|
||||
#[Autowire(expression: 'service("configuration.extension").get("my_extension", "mySetting")')]
|
||||
private readonly string $myExtensionSetting
|
||||
) {}
|
||||
}
|
||||
```
|
||||
|
||||
## PSR-14 Event Dispatcher
|
||||
|
||||
### Defining Custom Events
|
||||
|
||||
```php
|
||||
// ✅ Right: Immutable event class with getters/setters
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Vendor\ExtensionKey\Event;
|
||||
|
||||
final class BeforeUserCreatedEvent
|
||||
{
|
||||
public function __construct(
|
||||
private string $username,
|
||||
private string $email,
|
||||
private array $additionalData = []
|
||||
) {}
|
||||
|
||||
public function getUsername(): string
|
||||
{
|
||||
return $this->username;
|
||||
}
|
||||
|
||||
public function getEmail(): string
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public function getAdditionalData(): array
|
||||
{
|
||||
return $this->additionalData;
|
||||
}
|
||||
|
||||
public function setAdditionalData(array $additionalData): void
|
||||
{
|
||||
$this->additionalData = $additionalData;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Dispatching Events
|
||||
|
||||
```php
|
||||
// ✅ Right: Inject and dispatch events
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Vendor\ExtensionKey\Service;
|
||||
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Vendor\ExtensionKey\Event\BeforeUserCreatedEvent;
|
||||
|
||||
final class UserService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EventDispatcherInterface $eventDispatcher
|
||||
) {}
|
||||
|
||||
public function createUser(string $username, string $email): void
|
||||
{
|
||||
$event = new BeforeUserCreatedEvent($username, $email);
|
||||
$event = $this->eventDispatcher->dispatch($event);
|
||||
|
||||
// Use potentially modified data from event
|
||||
$finalUsername = $event->getUsername();
|
||||
$finalEmail = $event->getEmail();
|
||||
|
||||
// Create user with final data
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Event Listeners
|
||||
|
||||
```php
|
||||
// ✅ Right: Event listener with AsEventListener attribute
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Vendor\ExtensionKey\EventListener;
|
||||
|
||||
use TYPO3\CMS\Core\Attribute\AsEventListener;
|
||||
use Vendor\ExtensionKey\Event\BeforeUserCreatedEvent;
|
||||
|
||||
#[AsEventListener(
|
||||
identifier: 'vendor/extension-key/validate-user-creation',
|
||||
event: BeforeUserCreatedEvent::class
|
||||
)]
|
||||
final class ValidateUserCreationListener
|
||||
{
|
||||
public function __invoke(BeforeUserCreatedEvent $event): void
|
||||
{
|
||||
// Validate email format
|
||||
if (!filter_var($event->getEmail(), FILTER_VALIDATE_EMAIL)) {
|
||||
throw new \InvalidArgumentException('Invalid email format');
|
||||
}
|
||||
|
||||
// Add custom data
|
||||
$event->setAdditionalData([
|
||||
'validated_at' => time(),
|
||||
'validator' => 'ValidateUserCreationListener',
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Event Listener Registration (Services.yaml)
|
||||
|
||||
```yaml
|
||||
# Alternative: Register event listeners in Services.yaml
|
||||
services:
|
||||
Vendor\ExtensionKey\EventListener\ValidateUserCreationListener:
|
||||
tags:
|
||||
- name: event.listener
|
||||
identifier: 'vendor/extension-key/validate-user-creation'
|
||||
event: Vendor\ExtensionKey\Event\BeforeUserCreatedEvent
|
||||
method: '__invoke'
|
||||
```
|
||||
|
||||
### PSR-14 Event Class Standards (TYPO3 13+)
|
||||
|
||||
Modern event classes should follow these quality standards:
|
||||
|
||||
```php
|
||||
// ✅ Right: Modern event class with final keyword and readonly properties
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Vendor\ExtensionKey\Event;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
final class NewsListActionEvent // ✅ Use 'final' keyword
|
||||
{
|
||||
public function __construct(
|
||||
private NewsController $newsController,
|
||||
private array $assignedValues,
|
||||
private readonly ServerRequestInterface $request // ✅ Use 'readonly' for immutable properties
|
||||
) {}
|
||||
|
||||
public function getNewsController(): NewsController
|
||||
{
|
||||
return $this->newsController;
|
||||
}
|
||||
|
||||
public function getAssignedValues(): array
|
||||
{
|
||||
return $this->assignedValues;
|
||||
}
|
||||
|
||||
public function setAssignedValues(array $assignedValues): void
|
||||
{
|
||||
$this->assignedValues = $assignedValues;
|
||||
}
|
||||
|
||||
public function getRequest(): ServerRequestInterface
|
||||
{
|
||||
return $this->request; // Read-only, no setter
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Event Class Quality Checklist:**
|
||||
- [ ] Use `final` keyword (prevents inheritance, ensures immutability)
|
||||
- [ ] Use `readonly` for properties that should never change after construction
|
||||
- [ ] Provide getters for all properties
|
||||
- [ ] Provide setters ONLY for properties that should be modifiable
|
||||
- [ ] Type hint all properties and methods
|
||||
- [ ] Document the purpose and usage of the event
|
||||
|
||||
**Why `final` for Events?**
|
||||
- Events are data carriers, not meant to be extended
|
||||
- Prevents unexpected behavior from inheritance
|
||||
- Makes event behavior predictable and testable
|
||||
- Follows modern PHP best practices
|
||||
|
||||
**Why `readonly` for Properties?**
|
||||
- Some event data should never change (e.g., original request, user context)
|
||||
- Explicit immutability prevents accidental modifications
|
||||
- Clearly communicates intent to event listeners
|
||||
- Available in PHP 8.1+ (TYPO3 13 minimum is PHP 8.1)
|
||||
|
||||
## TYPO3 13 Site Sets
|
||||
|
||||
**Purpose:** Modern configuration distribution system replacing static TypoScript includes
|
||||
|
||||
### Site Sets Structure
|
||||
|
||||
```
|
||||
Configuration/Sets/
|
||||
├── MyExtension/ # Base configuration set
|
||||
│ ├── config.yaml # Set metadata and dependencies
|
||||
│ ├── setup.typoscript # Frontend TypoScript
|
||||
│ ├── constants.typoscript
|
||||
│ └── settings.definitions.yaml # Setting definitions for extension configuration
|
||||
├── RecordLinks/ # Optional feature set
|
||||
│ ├── config.yaml
|
||||
│ └── setup.typoscript
|
||||
└── Bootstrap5/ # Frontend framework preset
|
||||
├── config.yaml
|
||||
├── setup.typoscript
|
||||
└── settings.yaml
|
||||
```
|
||||
|
||||
### config.yaml Structure
|
||||
|
||||
```yaml
|
||||
# ✅ Right: Proper Site Set configuration
|
||||
name: vendor/extension-key
|
||||
label: Extension Name Base Configuration
|
||||
|
||||
# Dependencies on other sets
|
||||
dependencies:
|
||||
- typo3/fluid-styled-content
|
||||
- vendor/extension-key-styles
|
||||
|
||||
# Load order priority (optional)
|
||||
priority: 50
|
||||
|
||||
# Settings that can be overridden
|
||||
settings:
|
||||
mySetting:
|
||||
value: 'default value'
|
||||
type: string
|
||||
label: 'My Setting Label'
|
||||
description: 'Description of what this setting does'
|
||||
```
|
||||
|
||||
### settings.definitions.yaml
|
||||
|
||||
```yaml
|
||||
# ✅ Right: Define extension settings with validation
|
||||
settings:
|
||||
# Text input
|
||||
mySetting:
|
||||
type: string
|
||||
default: 'default value'
|
||||
label: 'LLL:EXT:extension_key/Resources/Private/Language/locallang.xlf:settings.mySetting'
|
||||
description: 'LLL:EXT:extension_key/Resources/Private/Language/locallang.xlf:settings.mySetting.description'
|
||||
|
||||
# Boolean checkbox
|
||||
enableFeature:
|
||||
type: bool
|
||||
default: false
|
||||
label: 'Enable Feature'
|
||||
|
||||
# Integer input
|
||||
itemsPerPage:
|
||||
type: int
|
||||
default: 10
|
||||
label: 'Items per page'
|
||||
validators:
|
||||
- name: NumberRange
|
||||
options:
|
||||
minimum: 1
|
||||
maximum: 100
|
||||
|
||||
# Select dropdown
|
||||
layout:
|
||||
type: string
|
||||
default: 'default'
|
||||
label: 'Layout'
|
||||
enum:
|
||||
default: 'Default'
|
||||
compact: 'Compact'
|
||||
detailed: 'Detailed'
|
||||
```
|
||||
|
||||
### Benefits of Site Sets
|
||||
|
||||
1. **Modular Configuration**: Split configuration into focused, reusable sets
|
||||
2. **Dependency Management**: Declare dependencies on other sets
|
||||
3. **Override Capability**: Sites can override set settings without editing files
|
||||
4. **Type Safety**: Settings are validated with defined types
|
||||
5. **Better UX**: Settings UI auto-generated from definitions
|
||||
6. **Version Control**: Configuration changes tracked properly
|
||||
|
||||
### Migration from Static TypoScript
|
||||
|
||||
```php
|
||||
// ❌ Old: Static TypoScript includes (TYPO3 12 and earlier)
|
||||
Configuration/TCA/Overrides/sys_template.php:
|
||||
<?php
|
||||
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addStaticFile(
|
||||
'extension_key',
|
||||
'Configuration/TypoScript',
|
||||
'Extension Name'
|
||||
);
|
||||
```
|
||||
|
||||
```yaml
|
||||
# ✅ New: Site Sets (TYPO3 13+)
|
||||
Configuration/Sets/ExtensionKey/config.yaml:
|
||||
name: vendor/extension-key
|
||||
label: Extension Name
|
||||
```
|
||||
|
||||
**Site Sets Conformance Checklist:**
|
||||
- [ ] Configuration/Sets/ directory exists
|
||||
- [ ] At least one base set with config.yaml
|
||||
- [ ] settings.definitions.yaml defines all extension settings
|
||||
- [ ] Set names follow vendor/package naming convention
|
||||
- [ ] Dependencies declared in config.yaml
|
||||
- [ ] Labels use LLL: references for translations
|
||||
- [ ] Settings have appropriate type validation
|
||||
|
||||
## Advanced Services.yaml Patterns
|
||||
|
||||
Beyond basic service registration, modern TYPO3 extensions use advanced Services.yaml patterns.
|
||||
|
||||
### Event Listeners
|
||||
|
||||
```yaml
|
||||
# ✅ Right: Event listener registration
|
||||
services:
|
||||
Vendor\ExtensionKey\EventListener\HrefLangEventListener:
|
||||
tags:
|
||||
- name: event.listener
|
||||
identifier: 'ext-extension-key/modify-hreflang'
|
||||
event: TYPO3\CMS\Frontend\Event\ModifyHrefLangTagsEvent
|
||||
method: '__invoke'
|
||||
|
||||
# Multiple listeners for same event
|
||||
Vendor\ExtensionKey\EventListener\PageCacheListener:
|
||||
tags:
|
||||
- name: event.listener
|
||||
identifier: 'ext-extension-key/cache-before'
|
||||
event: TYPO3\CMS\Core\Cache\Event\BeforePageCacheIdentifierIsHashedEvent
|
||||
- name: event.listener
|
||||
identifier: 'ext-extension-key/cache-after'
|
||||
event: TYPO3\CMS\Core\Cache\Event\AfterPageCacheIdentifierIsHashedEvent
|
||||
```
|
||||
|
||||
### Console Commands
|
||||
|
||||
```yaml
|
||||
# ✅ Right: Console command registration
|
||||
services:
|
||||
Vendor\ExtensionKey\Command\ProxyClassRebuildCommand:
|
||||
tags:
|
||||
- name: 'console.command'
|
||||
command: 'extension:rebuildProxyClasses'
|
||||
description: 'Rebuild Extbase proxy classes'
|
||||
schedulable: false # Cannot be run via scheduler
|
||||
|
||||
Vendor\ExtensionKey\Command\CleanupCommand:
|
||||
tags:
|
||||
- name: 'console.command'
|
||||
command: 'extension:cleanup'
|
||||
description: 'Clean up old records'
|
||||
schedulable: true # Can be run via scheduler
|
||||
hidden: false # Visible in command list
|
||||
```
|
||||
|
||||
### Data Processors
|
||||
|
||||
```yaml
|
||||
# ✅ Right: Data processor registration for Fluid templates
|
||||
services:
|
||||
Vendor\ExtensionKey\DataProcessing\AddNewsToMenuProcessor:
|
||||
tags:
|
||||
- name: 'data.processor'
|
||||
identifier: 'add-news-to-menu'
|
||||
|
||||
Vendor\ExtensionKey\DataProcessing\CategoryProcessor:
|
||||
tags:
|
||||
- name: 'data.processor'
|
||||
identifier: 'category-processor'
|
||||
```
|
||||
|
||||
### Cache Services
|
||||
|
||||
```yaml
|
||||
# ✅ Right: Cache service configuration
|
||||
services:
|
||||
cache.extension_custom:
|
||||
class: TYPO3\CMS\Core\Cache\Frontend\VariableFrontend
|
||||
factory:
|
||||
- '@TYPO3\CMS\Core\Cache\CacheManager'
|
||||
- 'getCache'
|
||||
arguments:
|
||||
- 'extension_custom'
|
||||
```
|
||||
|
||||
### Advanced Service Patterns
|
||||
|
||||
```yaml
|
||||
# ✅ Right: Comprehensive Services.yaml with advanced patterns
|
||||
services:
|
||||
_defaults:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
public: false
|
||||
|
||||
# Auto-register all classes
|
||||
Vendor\ExtensionKey\:
|
||||
resource: '../Classes/*'
|
||||
exclude:
|
||||
- '../Classes/Domain/Model/*' # Exclude Extbase models
|
||||
|
||||
# Event Listeners
|
||||
Vendor\ExtensionKey\EventListener\NewsListActionListener:
|
||||
tags:
|
||||
- name: event.listener
|
||||
identifier: 'ext-extension-key/news-list'
|
||||
event: Vendor\ExtensionKey\Event\NewsListActionEvent
|
||||
|
||||
# Console Commands
|
||||
Vendor\ExtensionKey\Command\ImportCommand:
|
||||
tags:
|
||||
- name: 'console.command'
|
||||
command: 'news:import'
|
||||
description: 'Import news from external source'
|
||||
schedulable: true
|
||||
|
||||
# Data Processors
|
||||
Vendor\ExtensionKey\DataProcessing\MenuProcessor:
|
||||
tags:
|
||||
- name: 'data.processor'
|
||||
identifier: 'news-menu-processor'
|
||||
|
||||
# Cache Factory
|
||||
cache.news_category:
|
||||
class: TYPO3\CMS\Core\Cache\Frontend\VariableFrontend
|
||||
factory: ['@TYPO3\CMS\Core\Cache\CacheManager', 'getCache']
|
||||
arguments: ['news_category']
|
||||
|
||||
# ViewHelper registration (if needed for testing)
|
||||
Vendor\ExtensionKey\ViewHelpers\FormatViewHelper:
|
||||
public: true
|
||||
```
|
||||
|
||||
**Advanced Services.yaml Conformance Checklist:**
|
||||
- [ ] Event listeners registered with proper tags
|
||||
- [ ] Console commands tagged with schedulable flag
|
||||
- [ ] Data processors registered with unique identifiers
|
||||
- [ ] Cache services use factory pattern
|
||||
- [ ] ViewHelpers marked public if needed externally
|
||||
- [ ] Service tags include all required attributes (identifier, event, method)
|
||||
- [ ] Commands have meaningful names and descriptions
|
||||
|
||||
## PSR-15 Middleware
|
||||
|
||||
### Middleware Structure
|
||||
|
||||
```php
|
||||
// ✅ Right: PSR-15 middleware implementation
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Vendor\ExtensionKey\Middleware;
|
||||
|
||||
use Psr\Http\Message\ResponseFactoryInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
final class StatusCheckMiddleware implements MiddlewareInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ResponseFactoryInterface $responseFactory
|
||||
) {}
|
||||
|
||||
public function process(
|
||||
ServerRequestInterface $request,
|
||||
RequestHandlerInterface $handler
|
||||
): ResponseInterface {
|
||||
// Check for specific condition
|
||||
if (($request->getQueryParams()['status'] ?? null) === 'check') {
|
||||
$response = $this->responseFactory->createResponse(200, 'OK');
|
||||
$response->getBody()->write(json_encode([
|
||||
'status' => 'ok',
|
||||
'message' => 'System is healthy'
|
||||
]));
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
// Pass to next middleware
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Middleware Registration
|
||||
|
||||
```php
|
||||
// Configuration/RequestMiddlewares.php
|
||||
<?php
|
||||
return [
|
||||
'frontend' => [
|
||||
'vendor/extension-key/status-check' => [
|
||||
'target' => \Vendor\ExtensionKey\Middleware\StatusCheckMiddleware::class,
|
||||
'before' => [
|
||||
'typo3/cms-frontend/page-resolver',
|
||||
],
|
||||
'after' => [
|
||||
'typo3/cms-core/normalized-params-attribute',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
## Extbase Architecture
|
||||
|
||||
### Domain Models
|
||||
|
||||
```php
|
||||
// ✅ Right: Extbase domain model
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Vendor\ExtensionKey\Domain\Model;
|
||||
|
||||
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
|
||||
|
||||
class Product extends AbstractEntity
|
||||
{
|
||||
protected string $title = '';
|
||||
protected float $price = 0.0;
|
||||
protected bool $available = true;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function setTitle(string $title): void
|
||||
{
|
||||
$this->title = $title;
|
||||
}
|
||||
|
||||
public function getPrice(): float
|
||||
{
|
||||
return $this->price;
|
||||
}
|
||||
|
||||
public function setPrice(float $price): void
|
||||
{
|
||||
$this->price = $price;
|
||||
}
|
||||
|
||||
public function isAvailable(): bool
|
||||
{
|
||||
return $this->available;
|
||||
}
|
||||
|
||||
public function setAvailable(bool $available): void
|
||||
{
|
||||
$this->available = $available;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Repositories
|
||||
|
||||
```php
|
||||
// ✅ Right: Extbase repository with dependency injection
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Vendor\ExtensionKey\Domain\Repository;
|
||||
|
||||
use TYPO3\CMS\Extbase\Persistence\Repository;
|
||||
use Vendor\ExtensionKey\Domain\Model\Product;
|
||||
|
||||
class ProductRepository extends Repository
|
||||
{
|
||||
/**
|
||||
* Find products by price range
|
||||
*
|
||||
* @param float $minPrice
|
||||
* @param float $maxPrice
|
||||
* @return array<Product>
|
||||
*/
|
||||
public function findByPriceRange(float $minPrice, float $maxPrice): array
|
||||
{
|
||||
$query = $this->createQuery();
|
||||
$query->matching(
|
||||
$query->logicalAnd(
|
||||
$query->greaterThanOrEqual('price', $minPrice),
|
||||
$query->lessThanOrEqual('price', $maxPrice)
|
||||
)
|
||||
);
|
||||
return $query->execute()->toArray();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Controllers
|
||||
|
||||
```php
|
||||
// ✅ Right: Extbase controller with dependency injection
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Vendor\ExtensionKey\Controller;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
|
||||
use Vendor\ExtensionKey\Domain\Repository\ProductRepository;
|
||||
|
||||
final class ProductController extends ActionController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ProductRepository $productRepository
|
||||
) {}
|
||||
|
||||
public function listAction(): ResponseInterface
|
||||
{
|
||||
$products = $this->productRepository->findAll();
|
||||
$this->view->assign('products', $products);
|
||||
return $this->htmlResponse();
|
||||
}
|
||||
|
||||
public function showAction(int $productId): ResponseInterface
|
||||
{
|
||||
$product = $this->productRepository->findByUid($productId);
|
||||
$this->view->assign('product', $product);
|
||||
return $this->htmlResponse();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Validators
|
||||
|
||||
```php
|
||||
// ✅ Right: Extbase validator with dependency injection
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Vendor\ExtensionKey\Domain\Validator;
|
||||
|
||||
use TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator;
|
||||
use Vendor\ExtensionKey\Domain\Repository\ProductRepository;
|
||||
|
||||
class UniqueProductTitleValidator extends AbstractValidator
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ProductRepository $productRepository
|
||||
) {}
|
||||
|
||||
protected function isValid(mixed $value): void
|
||||
{
|
||||
if (!is_string($value)) {
|
||||
$this->addError('Value must be a string', 1234567890);
|
||||
return;
|
||||
}
|
||||
|
||||
$existingProduct = $this->productRepository->findOneByTitle($value);
|
||||
if ($existingProduct !== null) {
|
||||
$this->addError(
|
||||
'Product with title "%s" already exists',
|
||||
1234567891,
|
||||
[$value]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Factory Pattern
|
||||
|
||||
```php
|
||||
// ✅ Right: Factory for Connection objects
|
||||
services:
|
||||
Vendor\ExtensionKey\Domain\Repository\MyRepository:
|
||||
factory: ['@TYPO3\CMS\Core\Database\ConnectionPool', 'getConnectionForTable']
|
||||
arguments:
|
||||
- 'my_table'
|
||||
```
|
||||
|
||||
### Singleton Services
|
||||
|
||||
```php
|
||||
// ✅ Right: Use DI container, not Singleton pattern
|
||||
// Services are automatically singleton by default
|
||||
|
||||
// ❌ Wrong: Don't use GeneralUtility::makeInstance() for new code
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
$service = GeneralUtility::makeInstance(MyService::class); // Deprecated
|
||||
```
|
||||
|
||||
### PSR Interfaces
|
||||
|
||||
```php
|
||||
// ✅ Right: Use PSR interfaces
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Client\ClientInterface;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Clock\ClockInterface;
|
||||
|
||||
// Inject PSR-compliant services
|
||||
public function __construct(
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly ClockInterface $clock
|
||||
) {}
|
||||
```
|
||||
|
||||
## Anti-Patterns to Avoid
|
||||
|
||||
### ❌ Wrong: Direct instantiation
|
||||
```php
|
||||
$repository = new ProductRepository(); // Missing dependencies
|
||||
```
|
||||
|
||||
### ❌ Wrong: Using GeneralUtility::makeInstance()
|
||||
```php
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
$repository = GeneralUtility::makeInstance(ProductRepository::class);
|
||||
```
|
||||
|
||||
### ❌ Wrong: Global state access
|
||||
```php
|
||||
$user = $GLOBALS['BE_USER']; // Avoid global state
|
||||
$typoScript = $GLOBALS['TSFE']->tmpl->setup;
|
||||
```
|
||||
|
||||
### ✅ Right: Dependency injection
|
||||
```php
|
||||
public function __construct(
|
||||
private readonly ProductRepository $repository,
|
||||
private readonly Context $context
|
||||
) {}
|
||||
```
|
||||
|
||||
## Conformance Checklist
|
||||
|
||||
### Basic Dependency Injection
|
||||
- [ ] Constructor injection used for all dependencies
|
||||
- [ ] Services registered in Configuration/Services.yaml
|
||||
- [ ] No direct class instantiation (new MyClass())
|
||||
- [ ] No GeneralUtility::makeInstance() for new services
|
||||
- [ ] PSR interfaces used (ResponseInterface, LoggerInterface, etc.)
|
||||
- [ ] No global state access ($GLOBALS)
|
||||
|
||||
### PSR-14 Events (Mandatory)
|
||||
- [ ] PSR-14 events used instead of hooks
|
||||
- [ ] Event classes are immutable with proper getters/setters
|
||||
- [ ] Event listeners use #[AsEventListener] attribute or Services.yaml tags
|
||||
- [ ] Event classes use `final` keyword (TYPO3 13+)
|
||||
- [ ] Event classes use `readonly` for immutable properties (TYPO3 13+)
|
||||
|
||||
### TYPO3 13 Site Sets (Mandatory for TYPO3 13)
|
||||
- [ ] Configuration/Sets/ directory exists
|
||||
- [ ] Base set has config.yaml with proper metadata
|
||||
- [ ] settings.definitions.yaml defines extension settings with types
|
||||
- [ ] Set names follow vendor/package convention
|
||||
- [ ] Dependencies declared in config.yaml
|
||||
|
||||
### Advanced Services.yaml (Mandatory)
|
||||
- [ ] Event listeners registered with proper tags
|
||||
- [ ] Console commands tagged with schedulable flag
|
||||
- [ ] Data processors registered with unique identifiers
|
||||
- [ ] Cache services use factory pattern
|
||||
- [ ] Service tags include all required attributes
|
||||
|
||||
### PSR-15 Middleware
|
||||
- [ ] PSR-15 middlewares registered in RequestMiddlewares.php
|
||||
- [ ] Middleware ordering defined with before/after
|
||||
|
||||
### Extbase Architecture
|
||||
- [ ] Extbase models extend AbstractEntity
|
||||
- [ ] Repositories extend Repository base class
|
||||
- [ ] Controllers use constructor injection
|
||||
- [ ] Validators extend AbstractValidator
|
||||
|
||||
### Factory Pattern
|
||||
- [ ] Factory pattern for complex object creation (e.g., Connection objects)
|
||||
421
skills/typo3-conformance/references/runtests-validation.md
Normal file
421
skills/typo3-conformance/references/runtests-validation.md
Normal file
@@ -0,0 +1,421 @@
|
||||
# runTests.sh Validation Guide
|
||||
|
||||
**Purpose:** Validate Build/Scripts/runTests.sh against TYPO3 Best Practices (Tea extension reference)
|
||||
|
||||
## Why Validate runTests.sh?
|
||||
|
||||
The `runTests.sh` script is the **central orchestration tool** for TYPO3 extension quality workflows. An outdated or misconfigured script can lead to:
|
||||
|
||||
- ❌ Testing with wrong PHP/TYPO3 versions (false positives/negatives)
|
||||
- ❌ Missing database compatibility issues
|
||||
- ❌ Inconsistent local vs CI environments
|
||||
- ❌ Developer confusion with incorrect defaults
|
||||
|
||||
## Reference Implementation
|
||||
|
||||
**Source of Truth:** https://github.com/TYPO3BestPractices/tea/blob/main/Build/Scripts/runTests.sh
|
||||
|
||||
The Tea extension maintains the canonical runTests.sh implementation, updated for latest TYPO3 standards.
|
||||
|
||||
## Critical Validation Points
|
||||
|
||||
### 1. PHP Version Configuration
|
||||
|
||||
**Check Lines ~318 and ~365:**
|
||||
|
||||
```bash
|
||||
# Default PHP version
|
||||
PHP_VERSION="X.X"
|
||||
|
||||
# PHP version validation regex
|
||||
if ! [[ ${PHP_VERSION} =~ ^(X.X|X.X|X.X)$ ]]; then
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
1. Read extension's composer.json `require.php` constraint
|
||||
2. Extract minimum PHP version (e.g., `^8.2` → minimum 8.2)
|
||||
3. Verify runTests.sh default matches minimum
|
||||
4. Verify version regex includes all supported versions
|
||||
|
||||
**Example Check:**
|
||||
|
||||
```bash
|
||||
# Extension composer.json
|
||||
"require": {
|
||||
"php": "^8.2 || ^8.3 || ^8.4"
|
||||
}
|
||||
|
||||
# runTests.sh SHOULD have:
|
||||
PHP_VERSION="8.2" # ✅ Matches minimum
|
||||
if ! [[ ${PHP_VERSION} =~ ^(8.2|8.3|8.4)$ ]]; then # ✅ All supported
|
||||
|
||||
# runTests.sh SHOULD NOT have:
|
||||
PHP_VERSION="7.4" # ❌ Below minimum
|
||||
if ! [[ ${PHP_VERSION} =~ ^(7.4|8.0|8.1|8.2|8.3)$ ]]; then # ❌ Includes unsupported
|
||||
```
|
||||
|
||||
**Severity:** 🔴 **High** - Testing with wrong PHP version invalidates results
|
||||
|
||||
### 2. TYPO3 Version Configuration
|
||||
|
||||
**Check Lines ~315 and ~374:**
|
||||
|
||||
```bash
|
||||
# Default TYPO3 version
|
||||
TYPO3_VERSION="XX"
|
||||
|
||||
# TYPO3 version validation
|
||||
if ! [[ ${TYPO3_VERSION} =~ ^(11|12|13)$ ]]; then
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
1. Read extension's composer.json TYPO3 core dependency
|
||||
2. Extract target TYPO3 version (e.g., `^13.4` → TYPO3 13)
|
||||
3. Verify runTests.sh default matches target
|
||||
4. Check composerInstallHighest/Lowest version constraints
|
||||
|
||||
**Example Check:**
|
||||
|
||||
```bash
|
||||
# Extension composer.json
|
||||
"require": {
|
||||
"typo3/cms-core": "^13.4"
|
||||
}
|
||||
|
||||
# runTests.sh SHOULD have:
|
||||
TYPO3_VERSION="13" # ✅ Matches target
|
||||
|
||||
# In composerInstallHighest (line ~530):
|
||||
if [ ${TYPO3_VERSION} -eq 13 ]; then
|
||||
composer require --no-ansi --no-interaction --no-progress --no-install \
|
||||
typo3/cms-core:^13.4 # ✅ Matches composer.json
|
||||
|
||||
# runTests.sh SHOULD NOT have:
|
||||
TYPO3_VERSION="11" # ❌ Below target
|
||||
```
|
||||
|
||||
**Severity:** 🔴 **High** - Testing against wrong TYPO3 version
|
||||
|
||||
### 3. Database Version Support
|
||||
|
||||
**Check Lines ~48-107 (handleDbmsOptions function):**
|
||||
|
||||
```bash
|
||||
mariadb)
|
||||
[ -z "${DBMS_VERSION}" ] && DBMS_VERSION="X.X"
|
||||
if ! [[ ${DBMS_VERSION} =~ ^(10.2|10.3|...|11.1)$ ]]; then
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
1. Check MariaDB, MySQL, PostgreSQL version lists are current
|
||||
2. Verify default versions are maintained (not EOL)
|
||||
3. Cross-reference with TYPO3 core database support matrix
|
||||
|
||||
**Current Database Support (TYPO3 13):**
|
||||
|
||||
| DBMS | Supported Versions | Default | EOL Status |
|
||||
|------|-------------------|---------|------------|
|
||||
| MariaDB | 10.4-10.11, 11.0-11.4 | 10.11 | 10.4+ maintained |
|
||||
| MySQL | 8.0, 8.1, 8.2, 8.3, 8.4 | 8.0 | 8.0 maintained until 2026 |
|
||||
| PostgreSQL | 10-16 | 16 | 10-11 EOL, 12+ maintained |
|
||||
| SQLite | 3.x | 3.x | Always latest |
|
||||
|
||||
**Example Check:**
|
||||
|
||||
```bash
|
||||
# runTests.sh MariaDB (line ~48)
|
||||
[ -z "${DBMS_VERSION}" ] && DBMS_VERSION="10.11" # ✅ LTS version
|
||||
if ! [[ ${DBMS_VERSION} =~ ^(10.4|10.5|10.6|10.11|11.0|11.1|11.2|11.3|11.4)$ ]]; then
|
||||
|
||||
# ❌ BAD - EOL version as default:
|
||||
[ -z "${DBMS_VERSION}" ] && DBMS_VERSION="10.2" # EOL 2023
|
||||
|
||||
# runTests.sh PostgreSQL (line ~79)
|
||||
[ -z "${DBMS_VERSION}" ] && DBMS_VERSION="16" # ✅ Latest stable
|
||||
if ! [[ ${DBMS_VERSION} =~ ^(10|11|12|13|14|15|16)$ ]]; then
|
||||
```
|
||||
|
||||
**Severity:** 🟡 **Medium** - May miss database-specific compatibility issues
|
||||
|
||||
### 4. Network Name Configuration
|
||||
|
||||
**Check Line ~331:**
|
||||
|
||||
```bash
|
||||
NETWORK="extension-name-${SUFFIX}"
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
1. Should match extension key or composer package name
|
||||
2. Should NOT be hardcoded to "friendsoftypo3-tea" (copy-paste artifact)
|
||||
|
||||
**Example Check:**
|
||||
|
||||
```bash
|
||||
# Extension key: rte_ckeditor_image
|
||||
# Composer package: netresearch/rte-ckeditor-image
|
||||
|
||||
# ✅ Good options:
|
||||
NETWORK="rte-ckeditor-image-${SUFFIX}"
|
||||
NETWORK="netresearch-rte-ckeditor-image-${SUFFIX}"
|
||||
|
||||
# ❌ Bad (copy-paste from Tea):
|
||||
NETWORK="friendsoftypo3-tea-${SUFFIX}"
|
||||
```
|
||||
|
||||
**Severity:** 🟢 **Low** - Cosmetic, but indicates lack of customization
|
||||
|
||||
### 5. Test Suite Commands
|
||||
|
||||
**Check Lines ~580, ~620 (functional and unit test commands):**
|
||||
|
||||
```bash
|
||||
functional)
|
||||
COMMAND=(.Build/bin/phpunit -c Build/phpunit/FunctionalTests.xml ...)
|
||||
|
||||
unit)
|
||||
COMMAND=(.Build/bin/phpunit -c Build/phpunit/UnitTests.xml ...)
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
1. Paths match actual PHPUnit config locations
|
||||
2. Config files exist and are properly named
|
||||
3. Exclude groups match available database types
|
||||
|
||||
**Example Check:**
|
||||
|
||||
```bash
|
||||
# Verify config files exist:
|
||||
ls -la Build/phpunit/UnitTests.xml # Must exist
|
||||
ls -la Build/phpunit/FunctionalTests.xml # Must exist
|
||||
|
||||
# Check command paths:
|
||||
COMMAND=(.Build/bin/phpunit -c Build/phpunit/FunctionalTests.xml ...)
|
||||
└─────┬─────┘ └──────────┬───────────┘
|
||||
✅ Matches ✅ Matches actual
|
||||
.Build/bin/ Build/phpunit/
|
||||
from composer.json directory structure
|
||||
```
|
||||
|
||||
**Severity:** 🔴 **High** - Tests won't run if paths are wrong
|
||||
|
||||
### 6. Container Image Versions
|
||||
|
||||
**Check Lines ~446-451:**
|
||||
|
||||
```bash
|
||||
IMAGE_PHP="ghcr.io/typo3/core-testing-$(echo "php${PHP_VERSION}" | sed -e 's/\.//'):latest"
|
||||
IMAGE_ALPINE="docker.io/alpine:3.8"
|
||||
IMAGE_DOCS="ghcr.io/typo3-documentation/render-guides:latest"
|
||||
IMAGE_MARIADB="docker.io/mariadb:${DBMS_VERSION}"
|
||||
IMAGE_MYSQL="docker.io/mysql:${DBMS_VERSION}"
|
||||
IMAGE_POSTGRES="docker.io/postgres:${DBMS_VERSION}-alpine"
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
1. PHP testing image uses official TYPO3 images
|
||||
2. Alpine version is reasonably current (not ancient)
|
||||
3. Documentation renderer is latest official TYPO3 image
|
||||
|
||||
**Severity:** 🟢 **Low** - Usually works, but outdated Alpine may have issues
|
||||
|
||||
## Conformance Evaluation Workflow
|
||||
|
||||
### Step 1: Extract Extension Requirements
|
||||
|
||||
```bash
|
||||
# Read composer.json
|
||||
cat composer.json | jq -r '.require.php' # e.g., "^8.2 || ^8.3 || ^8.4"
|
||||
cat composer.json | jq -r '.require."typo3/cms-core"' # e.g., "^13.4"
|
||||
|
||||
# Parse minimum versions
|
||||
MIN_PHP=$(echo "^8.2 || ^8.3" | grep -oE '[0-9]+\.[0-9]+' | head -1) # 8.2
|
||||
TARGET_TYPO3=$(echo "^13.4" | grep -oE '[0-9]+') # 13
|
||||
```
|
||||
|
||||
### Step 2: Validate runTests.sh Defaults
|
||||
|
||||
```bash
|
||||
# Check PHP version default (line ~318)
|
||||
grep '^PHP_VERSION=' Build/Scripts/runTests.sh
|
||||
# Expected: PHP_VERSION="8.2" (matches MIN_PHP)
|
||||
|
||||
# Check TYPO3 version default (line ~315)
|
||||
grep '^TYPO3_VERSION=' Build/Scripts/runTests.sh
|
||||
# Expected: TYPO3_VERSION="13" (matches TARGET_TYPO3)
|
||||
```
|
||||
|
||||
### Step 3: Validate PHP Version Regex
|
||||
|
||||
```bash
|
||||
# Extract PHP version regex (line ~365)
|
||||
grep -A 2 'if ! \[\[ \${PHP_VERSION}' Build/Scripts/runTests.sh
|
||||
|
||||
# Expected pattern for "^8.2 || ^8.3 || ^8.4":
|
||||
# ^(8.2|8.3|8.4)$
|
||||
|
||||
# ❌ Outdated pattern:
|
||||
# ^(7.4|8.0|8.1|8.2|8.3)$
|
||||
```
|
||||
|
||||
### Step 4: Validate TYPO3 Version Constraints in Composer Install
|
||||
|
||||
```bash
|
||||
# Check composerInstallHighest TYPO3 13 block (line ~530)
|
||||
sed -n '/if \[ \${TYPO3_VERSION} -eq 13 \];/,/fi/p' Build/Scripts/runTests.sh
|
||||
|
||||
# Should match composer.json requirements:
|
||||
# typo3/cms-core:^13.4
|
||||
# typo3/cms-backend:^13.4
|
||||
# etc.
|
||||
```
|
||||
|
||||
### Step 5: Validate Network Name
|
||||
|
||||
```bash
|
||||
# Check network name (line ~331)
|
||||
grep '^NETWORK=' Build/Scripts/runTests.sh
|
||||
|
||||
# Extract extension key from composer.json or ext_emconf.php
|
||||
EXT_KEY=$(jq -r '.extra.typo3.cms."extension-key"' composer.json)
|
||||
|
||||
# Expected: NETWORK="${EXT_KEY}-${SUFFIX}" or similar
|
||||
# ❌ Wrong: NETWORK="friendsoftypo3-tea-${SUFFIX}"
|
||||
```
|
||||
|
||||
## Automated Validation Script
|
||||
|
||||
Create `scripts/validate-runtests.sh`:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
echo "🔍 Validating Build/Scripts/runTests.sh against extension requirements..."
|
||||
|
||||
# Extract requirements
|
||||
MIN_PHP=$(jq -r '.require.php' composer.json | grep -oE '[0-9]+\.[0-9]+' | head -1)
|
||||
TARGET_TYPO3=$(jq -r '.require."typo3/cms-core"' composer.json | grep -oE '^[0-9]+' | head -1)
|
||||
EXT_KEY=$(jq -r '.extra.typo3.cms."extension-key"' composer.json)
|
||||
|
||||
echo "📋 Extension Requirements:"
|
||||
echo " PHP: ${MIN_PHP}+"
|
||||
echo " TYPO3: ${TARGET_TYPO3}"
|
||||
echo " Extension Key: ${EXT_KEY}"
|
||||
echo ""
|
||||
|
||||
# Validate PHP version default
|
||||
RUNTESTS_PHP=$(grep '^PHP_VERSION=' Build/Scripts/runTests.sh | cut -d'"' -f2)
|
||||
if [ "${RUNTESTS_PHP}" != "${MIN_PHP}" ]; then
|
||||
echo "❌ PHP version mismatch: runTests.sh uses ${RUNTESTS_PHP}, should be ${MIN_PHP}"
|
||||
exit 1
|
||||
else
|
||||
echo "✅ PHP version default: ${RUNTESTS_PHP}"
|
||||
fi
|
||||
|
||||
# Validate TYPO3 version default
|
||||
RUNTESTS_TYPO3=$(grep '^TYPO3_VERSION=' Build/Scripts/runTests.sh | cut -d'"' -f2)
|
||||
if [ "${RUNTESTS_TYPO3}" != "${TARGET_TYPO3}" ]; then
|
||||
echo "❌ TYPO3 version mismatch: runTests.sh uses ${RUNTESTS_TYPO3}, should be ${TARGET_TYPO3}"
|
||||
exit 1
|
||||
else
|
||||
echo "✅ TYPO3 version default: ${RUNTESTS_TYPO3}"
|
||||
fi
|
||||
|
||||
# Validate network name
|
||||
NETWORK_NAME=$(grep '^NETWORK=' Build/Scripts/runTests.sh | cut -d'"' -f2 | sed 's/-${SUFFIX}$//')
|
||||
if [[ "${NETWORK_NAME}" == "friendsoftypo3-tea" ]]; then
|
||||
echo "⚠️ Network name is copy-paste from Tea extension: ${NETWORK_NAME}"
|
||||
echo " Should be: ${EXT_KEY}-\${SUFFIX}"
|
||||
else
|
||||
echo "✅ Network name: ${NETWORK_NAME}-\${SUFFIX}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ runTests.sh validation complete"
|
||||
```
|
||||
|
||||
## Conformance Report Integration
|
||||
|
||||
### When evaluating runTests.sh:
|
||||
|
||||
**In "Best Practices" Section:**
|
||||
|
||||
```markdown
|
||||
### Build Scripts
|
||||
|
||||
**runTests.sh Analysis:**
|
||||
|
||||
- ✅ Script present and executable
|
||||
- ✅ PHP version default matches composer.json minimum (8.2)
|
||||
- ✅ TYPO3 version default matches target (13)
|
||||
- ✅ PHP version regex includes all supported versions (8.2, 8.3, 8.4)
|
||||
- ⚠️ Network name uses Tea extension default (cosmetic issue)
|
||||
- ✅ Test suite commands match actual file structure
|
||||
- ✅ Database version support is current
|
||||
|
||||
**Or with issues:**
|
||||
|
||||
- ❌ PHP version default (7.4) below extension minimum (8.2)
|
||||
- File: Build/Scripts/runTests.sh:318
|
||||
- Severity: High
|
||||
- Fix: Change `PHP_VERSION="7.4"` to `PHP_VERSION="8.2"`
|
||||
|
||||
- ❌ TYPO3 version default (11) below extension target (13)
|
||||
- File: Build/Scripts/runTests.sh:315
|
||||
- Severity: High
|
||||
- Fix: Change `TYPO3_VERSION="11"` to `TYPO3_VERSION="13"`
|
||||
|
||||
- ❌ PHP version regex includes unsupported versions
|
||||
- File: Build/Scripts/runTests.sh:365
|
||||
- Current: `^(7.4|8.0|8.1|8.2|8.3)$`
|
||||
- Expected: `^(8.2|8.3|8.4)$`
|
||||
- Severity: Medium
|
||||
- Fix: Remove unsupported versions from regex
|
||||
```
|
||||
|
||||
## Scoring Impact
|
||||
|
||||
**Best Practices Score Deductions:**
|
||||
|
||||
| Issue | Severity | Score Impact |
|
||||
|-------|----------|--------------|
|
||||
| PHP version default outdated | High | -3 points |
|
||||
| TYPO3 version default outdated | High | -3 points |
|
||||
| PHP version regex includes unsupported | Medium | -2 points |
|
||||
| Database versions EOL | Medium | -2 points |
|
||||
| Network name copy-paste | Low | -1 point |
|
||||
| Missing runTests.sh | Critical | -10 points |
|
||||
|
||||
**Maximum deduction for runTests.sh issues:** -6 points (out of 20 for Best Practices)
|
||||
|
||||
## Quick Reference Checklist
|
||||
|
||||
**When evaluating Build/Scripts/runTests.sh:**
|
||||
|
||||
```
|
||||
□ File exists and is executable
|
||||
□ PHP_VERSION default matches composer.json minimum
|
||||
□ TYPO3_VERSION default matches composer.json target
|
||||
□ PHP version regex matches composer.json constraint exactly
|
||||
□ TYPO3_VERSION regex includes supported versions only
|
||||
□ Database version lists are current (not EOL)
|
||||
□ Database version defaults are maintained LTS versions
|
||||
□ Network name is customized (not "friendsoftypo3-tea")
|
||||
□ Test suite paths match actual directory structure
|
||||
□ Container images use official TYPO3 testing images
|
||||
```
|
||||
|
||||
**Comparison Strategy:**
|
||||
|
||||
1. Download latest Tea runTests.sh as reference
|
||||
2. Compare line-by-line for structural differences
|
||||
3. Validate version-specific values against extension requirements
|
||||
4. Flag any outdated patterns or hardcoded Tea-specific values
|
||||
|
||||
## Resources
|
||||
|
||||
- **Tea Extension runTests.sh:** https://github.com/TYPO3BestPractices/tea/blob/main/Build/Scripts/runTests.sh
|
||||
- **TYPO3 Testing Documentation:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/Testing/
|
||||
- **Database Compatibility:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Database/
|
||||
558
skills/typo3-conformance/references/testing-standards.md
Normal file
558
skills/typo3-conformance/references/testing-standards.md
Normal file
@@ -0,0 +1,558 @@
|
||||
# TYPO3 Testing Standards
|
||||
|
||||
**Source:** TYPO3 Core API Reference - Testing
|
||||
**Purpose:** Unit, functional, and acceptance testing standards for TYPO3 extensions
|
||||
|
||||
## Testing Framework
|
||||
|
||||
TYPO3 uses **typo3/testing-framework** for comprehensive testing:
|
||||
|
||||
```bash
|
||||
# Install testing framework
|
||||
composer require --dev \
|
||||
"typo3/testing-framework":"^8.0.9" \
|
||||
"phpunit/phpunit":"^10.5"
|
||||
```
|
||||
|
||||
## Unit Testing
|
||||
|
||||
### Unit Test Structure
|
||||
|
||||
```php
|
||||
// ✅ Right: Proper unit test structure
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Vendor\ExtensionKey\Tests\Unit\Service;
|
||||
|
||||
use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
|
||||
use Vendor\ExtensionKey\Service\CalculationService;
|
||||
|
||||
class CalculationServiceTest extends UnitTestCase
|
||||
{
|
||||
private CalculationService $subject;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->subject = new CalculationService();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function addReturnsCorrectSum(): void
|
||||
{
|
||||
$result = $this->subject->add(2, 3);
|
||||
$this->assertEquals(5, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function multiplyReturnsCorrectProduct(): void
|
||||
{
|
||||
$result = $this->subject->multiply(4, 5);
|
||||
$this->assertEquals(20, $result);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### PHPUnit Configuration
|
||||
|
||||
```xml
|
||||
<!-- Build/phpunit/UnitTests.xml -->
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
|
||||
bootstrap="../../vendor/typo3/testing-framework/Resources/Core/Build/UnitTestsBootstrap.php"
|
||||
colors="true"
|
||||
beStrictAboutTestsThatDoNotTestAnything="true"
|
||||
failOnWarning="true"
|
||||
failOnRisky="true"
|
||||
stopOnFailure="false"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Unit tests">
|
||||
<directory>../../Tests/Unit/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
</phpunit>
|
||||
```
|
||||
|
||||
### Running Unit Tests
|
||||
|
||||
```bash
|
||||
# Direct execution
|
||||
vendor/bin/phpunit -c Build/phpunit/UnitTests.xml
|
||||
|
||||
# DDEV execution
|
||||
ddev exec php vendor/bin/phpunit -c Build/phpunit/UnitTests.xml
|
||||
|
||||
# Run specific test
|
||||
vendor/bin/phpunit -c Build/phpunit/UnitTests.xml --filter "CalculationServiceTest"
|
||||
```
|
||||
|
||||
### Unit Test Best Practices
|
||||
|
||||
**✅ Do:**
|
||||
- Test single units (methods, functions) in isolation
|
||||
- Mock external dependencies
|
||||
- Test edge cases and boundary conditions
|
||||
- Use descriptive test method names
|
||||
- Follow naming: `methodName<Condition>Returns<Expected>`
|
||||
- Keep tests fast (no database, no external services)
|
||||
|
||||
**❌ Don't:**
|
||||
- Access database in unit tests
|
||||
- Depend on file system
|
||||
- Make HTTP requests
|
||||
- Test framework internals
|
||||
- Write integration tests as unit tests
|
||||
|
||||
## Functional Testing
|
||||
|
||||
### Functional Test Structure
|
||||
|
||||
```php
|
||||
// ✅ Right: Proper functional test structure
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Vendor\ExtensionKey\Tests\Functional\Domain\Repository;
|
||||
|
||||
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
|
||||
use Vendor\ExtensionKey\Domain\Repository\ProductRepository;
|
||||
|
||||
class ProductRepositoryTest extends FunctionalTestCase
|
||||
{
|
||||
protected array $testExtensionsToLoad = [
|
||||
'typo3conf/ext/my_extension',
|
||||
];
|
||||
|
||||
protected ProductRepository $subject;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// Load test data
|
||||
$this->importCSVDataSet(__DIR__ . '/Fixtures/products.csv');
|
||||
|
||||
// Set up backend user
|
||||
$this->setUpBackendUser(1);
|
||||
|
||||
// Initialize subject
|
||||
$this->subject = $this->get(ProductRepository::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function findAllReturnsAllProducts(): void
|
||||
{
|
||||
$products = $this->subject->findAll();
|
||||
$this->assertCount(3, $products);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function findByPriceRangeReturnsMatchingProducts(): void
|
||||
{
|
||||
$products = $this->subject->findByPriceRange(10.0, 50.0);
|
||||
$this->assertCount(2, $products);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### PHPUnit Functional Configuration
|
||||
|
||||
```xml
|
||||
<!-- Build/phpunit/FunctionalTests.xml -->
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
|
||||
bootstrap="../../vendor/typo3/testing-framework/Resources/Core/Build/FunctionalTestsBootstrap.php"
|
||||
colors="true"
|
||||
beStrictAboutTestsThatDoNotTestAnything="true"
|
||||
failOnWarning="true"
|
||||
failOnRisky="true"
|
||||
stopOnFailure="false"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Functional tests">
|
||||
<directory>../../Tests/Functional/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
</phpunit>
|
||||
```
|
||||
|
||||
### Running Functional Tests
|
||||
|
||||
```bash
|
||||
# With MySQL/MariaDB
|
||||
ddev exec \
|
||||
typo3DatabaseDriver='mysqli' \
|
||||
typo3DatabaseHost='db' \
|
||||
typo3DatabasePort=3306 \
|
||||
typo3DatabaseUsername='root' \
|
||||
typo3DatabasePassword='root' \
|
||||
typo3DatabaseName='func' \
|
||||
php vendor/bin/phpunit -c Build/phpunit/FunctionalTests.xml
|
||||
|
||||
# With SQLite (simpler)
|
||||
ddev exec \
|
||||
typo3DatabaseDriver=pdo_sqlite \
|
||||
php vendor/bin/phpunit -c Build/phpunit/FunctionalTests.xml
|
||||
|
||||
# With PostgreSQL
|
||||
ddev exec \
|
||||
typo3DatabaseDriver='pdo_pgsql' \
|
||||
typo3DatabaseHost='postgres' \
|
||||
typo3DatabasePort=5432 \
|
||||
typo3DatabaseUsername='postgres' \
|
||||
typo3DatabasePassword='postgres' \
|
||||
typo3DatabaseName='func' \
|
||||
php vendor/bin/phpunit -c Build/phpunit/FunctionalTests.xml
|
||||
```
|
||||
|
||||
### Test Data Fixtures
|
||||
|
||||
```csv
|
||||
# Tests/Functional/Fixtures/products.csv
|
||||
tx_myext_product,uid,pid,title,price,available
|
||||
,1,0,Product A,29.99,1
|
||||
,2,0,Product B,49.99,1
|
||||
,3,0,Product C,99.99,0
|
||||
```
|
||||
|
||||
### Loading Extensions in Tests
|
||||
|
||||
```php
|
||||
// Load extension under test
|
||||
protected array $testExtensionsToLoad = [
|
||||
'typo3conf/ext/my_extension',
|
||||
];
|
||||
|
||||
// Load additional core extensions
|
||||
protected array $coreExtensionsToLoad = [
|
||||
'typo3/cms-workspaces',
|
||||
];
|
||||
|
||||
// Load fixture extensions
|
||||
protected array $testExtensionsToLoad = [
|
||||
'typo3conf/ext/my_extension',
|
||||
'typo3conf/ext/my_extension/Tests/Functional/Fixtures/Extensions/fixture_extension',
|
||||
];
|
||||
```
|
||||
|
||||
## Acceptance Testing
|
||||
|
||||
### Codeception Setup
|
||||
|
||||
```yaml
|
||||
# Tests/codeception.yml
|
||||
namespace: Vendor\ExtensionKey\Tests\Acceptance\Support
|
||||
suites:
|
||||
acceptance:
|
||||
actor: AcceptanceTester
|
||||
path: .
|
||||
modules:
|
||||
enabled:
|
||||
- Asserts
|
||||
- WebDriver:
|
||||
url: https://myproject.ddev.site
|
||||
browser: chrome
|
||||
host: ddev-myproject-chrome
|
||||
wait: 1
|
||||
window_size: 1280x1024
|
||||
|
||||
extensions:
|
||||
enabled:
|
||||
- Codeception\Extension\RunFailed
|
||||
- Codeception\Extension\Recorder
|
||||
|
||||
paths:
|
||||
tests: Acceptance
|
||||
output: ../var/log/_output
|
||||
data: .
|
||||
support: Acceptance/Support
|
||||
|
||||
settings:
|
||||
shuffle: false
|
||||
lint: true
|
||||
colors: true
|
||||
```
|
||||
|
||||
### Acceptance Test Structure
|
||||
|
||||
```php
|
||||
// ✅ Right: Backend acceptance test
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Vendor\ExtensionKey\Tests\Acceptance\Backend;
|
||||
|
||||
use Vendor\ExtensionKey\Tests\Acceptance\Support\BackendTester;
|
||||
use TYPO3\TestingFramework\Core\Acceptance\Helper\Topbar;
|
||||
|
||||
class ModuleCest
|
||||
{
|
||||
public function _before(BackendTester $I): void
|
||||
{
|
||||
$I->useExistingSession('admin');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BackendTester $I
|
||||
*/
|
||||
public function moduleCanBeAccessed(BackendTester $I): void
|
||||
{
|
||||
$I->click(Topbar::$dropdownToggleSelector, '#typo3-cms-backend-backend-toolbaritems-helptoolbaritem');
|
||||
$I->canSee('My Module');
|
||||
$I->click('My Module');
|
||||
$I->switchToContentFrame();
|
||||
$I->see('Module Content', 'h1');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BackendTester $I
|
||||
*/
|
||||
public function formSubmissionWorks(BackendTester $I): void
|
||||
{
|
||||
$I->amOnPage('/typo3/module/my-module');
|
||||
$I->switchToContentFrame();
|
||||
$I->fillField('title', 'Test Title');
|
||||
$I->click('Save');
|
||||
$I->see('Record saved successfully');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Frontend Acceptance Test
|
||||
|
||||
```php
|
||||
// ✅ Right: Frontend acceptance test
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Vendor\ExtensionKey\Tests\Acceptance\Frontend;
|
||||
|
||||
use Vendor\ExtensionKey\Tests\Acceptance\Support\AcceptanceTester;
|
||||
|
||||
class FrontendPagesCest
|
||||
{
|
||||
/**
|
||||
* @param AcceptanceTester $I
|
||||
*/
|
||||
public function homepageIsRendered(AcceptanceTester $I): void
|
||||
{
|
||||
$I->amOnPage('/');
|
||||
$I->see('Welcome to TYPO3');
|
||||
$I->seeElement('h1');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AcceptanceTester $I
|
||||
*/
|
||||
public function navigationWorks(AcceptanceTester $I): void
|
||||
{
|
||||
$I->amOnPage('/');
|
||||
$I->click('Products');
|
||||
$I->see('Our Products', 'h1');
|
||||
$I->seeInCurrentUrl('/products');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Running Acceptance Tests
|
||||
|
||||
```bash
|
||||
# Run acceptance tests via DDEV
|
||||
ddev exec bin/codecept run acceptance -d -c Tests/codeception.yml
|
||||
|
||||
# Run specific test
|
||||
ddev exec bin/codecept run acceptance ModuleCest -c Tests/codeception.yml
|
||||
|
||||
# Generate new test
|
||||
ddev exec bin/codecept generate:cest acceptance MyNewTest -c Tests/codeception.yml
|
||||
```
|
||||
|
||||
## Test Organization
|
||||
|
||||
### Directory Structure
|
||||
|
||||
```
|
||||
Tests/
|
||||
├── Unit/
|
||||
│ ├── Controller/
|
||||
│ │ └── ProductControllerTest.php
|
||||
│ ├── Domain/
|
||||
│ │ ├── Model/
|
||||
│ │ │ └── ProductTest.php
|
||||
│ │ └── Repository/
|
||||
│ │ └── ProductRepositoryTest.php
|
||||
│ └── Service/
|
||||
│ └── CalculationServiceTest.php
|
||||
├── Functional/
|
||||
│ ├── Domain/
|
||||
│ │ └── Repository/
|
||||
│ │ ├── ProductRepositoryTest.php
|
||||
│ │ └── Fixtures/
|
||||
│ │ └── products.csv
|
||||
│ └── Controller/
|
||||
│ └── ProductControllerTest.php
|
||||
└── Acceptance/
|
||||
├── Backend/
|
||||
│ └── ModuleCest.php
|
||||
├── Frontend/
|
||||
│ └── FrontendPagesCest.php
|
||||
└── Support/
|
||||
├── AcceptanceTester.php
|
||||
└── BackendTester.php
|
||||
```
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
**Unit Tests:**
|
||||
- Pattern: `<ClassName>Test.php`
|
||||
- Example: `ProductRepository.php` → `ProductRepositoryTest.php`
|
||||
- Location: Mirror `Classes/` structure in `Tests/Unit/`
|
||||
|
||||
**Functional Tests:**
|
||||
- Pattern: `<ClassName>Test.php`
|
||||
- Example: `ProductRepository.php` → `ProductRepositoryTest.php`
|
||||
- Location: Mirror `Classes/` structure in `Tests/Functional/`
|
||||
|
||||
**Acceptance Tests:**
|
||||
- Pattern: `<Feature>Cest.php`
|
||||
- Example: `ModuleCest.php`, `LoginCest.php`
|
||||
- Location: `Tests/Acceptance/Backend/` or `Tests/Acceptance/Frontend/`
|
||||
|
||||
## PHPUnit Attributes (PHP 8.0+)
|
||||
|
||||
```php
|
||||
// ✅ Right: Using PHPUnit attributes
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Vendor\ExtensionKey\Tests\Unit\Service;
|
||||
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
|
||||
|
||||
class CalculationServiceTest extends UnitTestCase
|
||||
{
|
||||
#[Test]
|
||||
public function addReturnsCorrectSum(): void
|
||||
{
|
||||
$this->assertEquals(5, $this->subject->add(2, 3));
|
||||
}
|
||||
|
||||
public static function priceDataProvider(): \Generator
|
||||
{
|
||||
yield 'standard price' => [
|
||||
'price' => 100.0,
|
||||
'taxRate' => 0.19,
|
||||
'expected' => 119.0,
|
||||
];
|
||||
yield 'zero price' => [
|
||||
'price' => 0.0,
|
||||
'taxRate' => 0.19,
|
||||
'expected' => 0.0,
|
||||
];
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[DataProvider('priceDataProvider')]
|
||||
public function calculatePriceWithTax(float $price, float $taxRate, float $expected): void
|
||||
{
|
||||
$result = $this->subject->calculatePriceWithTax($price, $taxRate);
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
```yaml
|
||||
# .github/workflows/tests.yml
|
||||
name: Tests
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
php: ['8.1', '8.2', '8.3']
|
||||
typo3: ['12.4', '13.0']
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install
|
||||
|
||||
- name: Lint PHP
|
||||
run: find . -name \*.php ! -path "./vendor/*" -exec php -l {} \;
|
||||
|
||||
- name: Unit Tests
|
||||
run: vendor/bin/phpunit -c Build/phpunit/UnitTests.xml
|
||||
|
||||
- name: Functional Tests
|
||||
run: |
|
||||
typo3DatabaseDriver=pdo_sqlite \
|
||||
vendor/bin/phpunit -c Build/phpunit/FunctionalTests.xml
|
||||
```
|
||||
|
||||
## Conformance Checklist
|
||||
|
||||
### Unit Tests
|
||||
- [ ] Unit tests extend `UnitTestCase`
|
||||
- [ ] Tests located in `Tests/Unit/` mirroring `Classes/`
|
||||
- [ ] Test files named `<ClassName>Test.php`
|
||||
- [ ] No database access in unit tests
|
||||
- [ ] No file system access in unit tests
|
||||
- [ ] All public methods tested
|
||||
- [ ] Edge cases and boundaries tested
|
||||
- [ ] #[Test] attribute or @test annotation used
|
||||
|
||||
### Functional Tests
|
||||
- [ ] Functional tests extend `FunctionalTestCase`
|
||||
- [ ] Tests located in `Tests/Functional/`
|
||||
- [ ] `setUp()` calls `parent::setUp()` first
|
||||
- [ ] Extensions loaded via `$testExtensionsToLoad`
|
||||
- [ ] Test data loaded via `importCSVDataSet()`
|
||||
- [ ] Database operations tested
|
||||
- [ ] Backend user initialized when needed
|
||||
|
||||
### Acceptance Tests
|
||||
- [ ] Acceptance tests use Codeception
|
||||
- [ ] Tests located in `Tests/Acceptance/`
|
||||
- [ ] Test files named `<Feature>Cest.php`
|
||||
- [ ] codeception.yml properly configured
|
||||
- [ ] Backend tests use `useExistingSession('admin')`
|
||||
- [ ] Frame switching used correctly
|
||||
- [ ] Tests verify user-visible behavior
|
||||
|
||||
### General
|
||||
- [ ] PHPUnit configuration files present
|
||||
- [ ] All tests pass locally
|
||||
- [ ] CI/CD pipeline configured
|
||||
- [ ] Test coverage >70% for new code
|
||||
- [ ] Data providers use named arguments
|
||||
- [ ] Descriptive test method names
|
||||
408
skills/typo3-conformance/references/v13-deprecations.md
Normal file
408
skills/typo3-conformance/references/v13-deprecations.md
Normal file
@@ -0,0 +1,408 @@
|
||||
# TYPO3 v13 Deprecations and Modern Alternatives
|
||||
|
||||
**Sources:** TYPO3 Core API Reference v13.4
|
||||
**Purpose:** Track v13 deprecations, migration paths, and modern configuration approaches
|
||||
|
||||
## Deprecated Files (v13.1+)
|
||||
|
||||
### ext_typoscript_constants.typoscript
|
||||
**Status:** DEPRECATED since TYPO3 v13.1
|
||||
|
||||
**Purpose:** Provided global TypoScript constants
|
||||
|
||||
**Migration Paths:**
|
||||
|
||||
**1. Preferred: Site Settings Definitions**
|
||||
```yaml
|
||||
# Configuration/Sets/MySet/settings.definitions.yaml
|
||||
settings:
|
||||
myext:
|
||||
itemsPerPage:
|
||||
type: int
|
||||
default: 10
|
||||
label: 'Items per page'
|
||||
```
|
||||
|
||||
**2. For Global Constants:**
|
||||
```php
|
||||
// ext_localconf.php
|
||||
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTypoScript(
|
||||
'my_extension',
|
||||
'constants',
|
||||
'@import "EXT:my_extension/Configuration/TypoScript/constants.typoscript"'
|
||||
);
|
||||
```
|
||||
|
||||
**Detection:**
|
||||
```bash
|
||||
[ -f "ext_typoscript_constants.typoscript" ] && echo "⚠️ DEPRECATED: Migrate to Site sets"
|
||||
```
|
||||
|
||||
**Impact:** "This file takes no effect in sites that use Site sets."
|
||||
|
||||
---
|
||||
|
||||
### ext_typoscript_setup.typoscript
|
||||
**Status:** DEPRECATED since TYPO3 v13.1
|
||||
|
||||
**Purpose:** Provided global TypoScript setup
|
||||
|
||||
**Migration Paths:**
|
||||
|
||||
**1. Preferred: Site Sets**
|
||||
```yaml
|
||||
# Configuration/Sets/MySet/config.yaml
|
||||
name: my-vendor/my-set
|
||||
label: 'My Extension Set'
|
||||
|
||||
imports:
|
||||
- { resource: 'EXT:fluid_styled_content/Configuration/Sets/FluidStyledContent/config.yaml' }
|
||||
```
|
||||
|
||||
```typoscript
|
||||
# Configuration/Sets/MySet/setup.typoscript
|
||||
plugin.tx_myextension {
|
||||
settings {
|
||||
itemsPerPage = 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**2. For Global Loading:**
|
||||
```php
|
||||
// ext_localconf.php
|
||||
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTypoScript(
|
||||
'my_extension',
|
||||
'setup',
|
||||
'@import "EXT:my_extension/Configuration/TypoScript/setup.typoscript"'
|
||||
);
|
||||
```
|
||||
|
||||
**Detection:**
|
||||
```bash
|
||||
[ -f "ext_typoscript_setup.typoscript" ] && echo "⚠️ DEPRECATED: Migrate to Site sets"
|
||||
```
|
||||
|
||||
**Impact:** "This file takes no effect in sites that use Site sets. This file works for backward compatibility reasons only in installations that depend on TypoScript records only."
|
||||
|
||||
---
|
||||
|
||||
## Deprecated Methods (Removal in v14)
|
||||
|
||||
### ExtensionManagementUtility::addUserTSConfig()
|
||||
**Status:** DEPRECATED, will be removed with TYPO3 v14.0
|
||||
|
||||
**Old Approach:**
|
||||
```php
|
||||
// ext_localconf.php - DEPRECATED
|
||||
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addUserTSConfig('
|
||||
options.pageTree.showPageIdWithTitle = 1
|
||||
options.defaultUploadFolder = 1:/user_uploads/
|
||||
');
|
||||
```
|
||||
|
||||
**Modern Approach:**
|
||||
```
|
||||
# Configuration/user.tsconfig
|
||||
options.pageTree.showPageIdWithTitle = 1
|
||||
options.defaultUploadFolder = 1:/user_uploads/
|
||||
```
|
||||
|
||||
**Detection:**
|
||||
```bash
|
||||
grep "addUserTSConfig" ext_localconf.php && echo "❌ DEPRECATED: Use Configuration/user.tsconfig"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Modern Configuration Files (v12+)
|
||||
|
||||
### Configuration/user.tsconfig
|
||||
**Since:** TYPO3 v12
|
||||
**Purpose:** User TSconfig loaded for all backend users
|
||||
|
||||
**Location:** `Configuration/user.tsconfig`
|
||||
|
||||
**Example:**
|
||||
```
|
||||
# Default user settings
|
||||
options.pageTree.showPageIdWithTitle = 1
|
||||
options.defaultUploadFolder = 1:/user_uploads/
|
||||
|
||||
# Hide modules
|
||||
options.hideModules = web_layout, web_info
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
[ -f "Configuration/user.tsconfig" ] && echo "✅ Modern user TSconfig" || echo "⚠️ Consider adding user TSconfig"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Configuration/page.tsconfig
|
||||
**Since:** TYPO3 v12
|
||||
**Purpose:** Page TSconfig loaded globally
|
||||
|
||||
**Location:** `Configuration/page.tsconfig`
|
||||
|
||||
**Example:**
|
||||
```
|
||||
# Default page configuration
|
||||
TCEFORM.pages.layout.disabled = 1
|
||||
TCEMAIN.table.pages.disablePrependAtCopy = 1
|
||||
|
||||
# Backend layout
|
||||
mod.web_layout.BackendLayouts {
|
||||
standard {
|
||||
title = Standard Layout
|
||||
icon = EXT:my_ext/Resources/Public/Icons/layout.svg
|
||||
config {
|
||||
backend_layout {
|
||||
colCount = 2
|
||||
rowCount = 1
|
||||
rows {
|
||||
1 {
|
||||
columns {
|
||||
1 {
|
||||
name = Main
|
||||
colPos = 0
|
||||
}
|
||||
2 {
|
||||
name = Sidebar
|
||||
colPos = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
[ -f "Configuration/page.tsconfig" ] && echo "✅ Modern page TSconfig" || echo "⚠️ Consider adding page TSconfig"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Modern Backend Configuration (v13)
|
||||
|
||||
### Configuration/Backend/Modules.php
|
||||
**Since:** TYPO3 v13.0
|
||||
**Purpose:** Backend module registration (replaces ext_tables.php)
|
||||
|
||||
**Location:** `Configuration/Backend/Modules.php`
|
||||
|
||||
**Example:**
|
||||
```php
|
||||
<?php
|
||||
|
||||
return [
|
||||
'web_myext' => [
|
||||
'parent' => 'web',
|
||||
'position' => ['after' => 'web_list'],
|
||||
'access' => 'user',
|
||||
'workspaces' => 'live',
|
||||
'path' => '/module/web/myext',
|
||||
'labels' => 'LLL:EXT:my_ext/Resources/Private/Language/locallang_mod.xlf',
|
||||
'extensionName' => 'MyExt',
|
||||
'controllerActions' => [
|
||||
\Vendor\MyExt\Controller\BackendController::class => [
|
||||
'list',
|
||||
'detail',
|
||||
'update',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
**Old Approach (DEPRECATED):**
|
||||
```php
|
||||
// ext_tables.php - DEPRECATED
|
||||
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerModule(
|
||||
'MyExt',
|
||||
'web',
|
||||
'mymodule',
|
||||
'after:list',
|
||||
[
|
||||
\Vendor\MyExt\Controller\BackendController::class => 'list,detail,update',
|
||||
],
|
||||
[
|
||||
'access' => 'user,group',
|
||||
'labels' => 'LLL:EXT:my_ext/Resources/Private/Language/locallang_mod.xlf',
|
||||
]
|
||||
);
|
||||
```
|
||||
|
||||
**Migration Script:** TYPO3 provides "Check TCA in ext_tables.php" upgrade tool
|
||||
|
||||
**Validation:**
|
||||
```bash
|
||||
[ -f "Configuration/Backend/Modules.php" ] && echo "✅ Modern backend modules" || echo "⚠️ Check for modules in ext_tables.php"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Site Sets (v13 Recommended Approach)
|
||||
|
||||
### Configuration/Sets Structure
|
||||
|
||||
```
|
||||
Configuration/Sets/
|
||||
└── MySet/
|
||||
├── config.yaml (REQUIRED)
|
||||
├── settings.definitions.yaml
|
||||
├── setup.typoscript
|
||||
├── constants.typoscript (optional)
|
||||
├── page.tsconfig
|
||||
└── user.tsconfig
|
||||
```
|
||||
|
||||
### config.yaml (Required)
|
||||
```yaml
|
||||
name: my-vendor/my-set
|
||||
label: 'My Extension Configuration Set'
|
||||
|
||||
# Dependencies
|
||||
imports:
|
||||
- { resource: 'EXT:fluid_styled_content/Configuration/Sets/FluidStyledContent/config.yaml' }
|
||||
|
||||
# Settings with defaults
|
||||
settings:
|
||||
myext:
|
||||
itemsPerPage: 10
|
||||
showImages: true
|
||||
```
|
||||
|
||||
### settings.definitions.yaml
|
||||
```yaml
|
||||
settings:
|
||||
myext:
|
||||
itemsPerPage:
|
||||
type: int
|
||||
default: 10
|
||||
label: 'Items per page'
|
||||
description: 'Number of items displayed per page in list view'
|
||||
|
||||
showImages:
|
||||
type: bool
|
||||
default: true
|
||||
label: 'Show images'
|
||||
description: 'Display images in list view'
|
||||
|
||||
templateLayout:
|
||||
type: string
|
||||
default: 'default'
|
||||
label: 'Template layout'
|
||||
enum:
|
||||
default: 'Default Layout'
|
||||
grid: 'Grid Layout'
|
||||
list: 'List Layout'
|
||||
```
|
||||
|
||||
### setup.typoscript
|
||||
```typoscript
|
||||
plugin.tx_myextension {
|
||||
view {
|
||||
templateRootPaths.0 = EXT:my_extension/Resources/Private/Templates/
|
||||
partialRootPaths.0 = EXT:my_extension/Resources/Private/Partials/
|
||||
layoutRootPaths.0 = EXT:my_extension/Resources/Private/Layouts/
|
||||
}
|
||||
|
||||
settings {
|
||||
itemsPerPage = {$settings.myext.itemsPerPage}
|
||||
showImages = {$settings.myext.showImages}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Activation in Site Configuration
|
||||
```yaml
|
||||
# config/sites/mysite/config.yaml
|
||||
base: 'https://example.com/'
|
||||
rootPageId: 1
|
||||
sets:
|
||||
- my-vendor/my-set # Activates the set
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration Checklist
|
||||
|
||||
### For v12 → v13 Migration
|
||||
- [ ] Move backend module registration from ext_tables.php to Configuration/Backend/Modules.php
|
||||
- [ ] Replace `addUserTSConfig()` with Configuration/user.tsconfig
|
||||
- [ ] Move page TSconfig from ext_localconf.php to Configuration/page.tsconfig
|
||||
- [ ] Deprecate ext_typoscript_constants.typoscript (use Site sets)
|
||||
- [ ] Deprecate ext_typoscript_setup.typoscript (use Site sets)
|
||||
|
||||
### For Modern v13 Extensions
|
||||
- [ ] Use Configuration/Sets/ for TypoScript configuration
|
||||
- [ ] Use settings.definitions.yaml for extension settings
|
||||
- [ ] Use Configuration/Backend/Modules.php for backend modules
|
||||
- [ ] Use Configuration/user.tsconfig for user TSconfig
|
||||
- [ ] Use Configuration/page.tsconfig for page TSconfig
|
||||
- [ ] Use Configuration/Icons.php for icon registration
|
||||
|
||||
---
|
||||
|
||||
## Validation Commands
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# check-v13-deprecations.sh
|
||||
|
||||
echo "=== Checking for TYPO3 v13 Deprecations ==="
|
||||
echo ""
|
||||
|
||||
# Check deprecated files
|
||||
if [ -f "ext_typoscript_constants.typoscript" ]; then
|
||||
echo "⚠️ DEPRECATED: ext_typoscript_constants.typoscript (v13.1)"
|
||||
echo " → Migrate to Configuration/Sets/ with settings.definitions.yaml"
|
||||
fi
|
||||
|
||||
if [ -f "ext_typoscript_setup.typoscript" ]; then
|
||||
echo "⚠️ DEPRECATED: ext_typoscript_setup.typoscript (v13.1)"
|
||||
echo " → Migrate to Configuration/Sets/ with setup.typoscript"
|
||||
fi
|
||||
|
||||
# Check deprecated methods
|
||||
if grep -q "addUserTSConfig" ext_localconf.php 2>/dev/null; then
|
||||
echo "❌ DEPRECATED: addUserTSConfig() - Removal in v14"
|
||||
echo " → Use Configuration/user.tsconfig"
|
||||
fi
|
||||
|
||||
# Check for backend modules in ext_tables.php
|
||||
if grep -q "registerModule" ext_tables.php 2>/dev/null; then
|
||||
echo "⚠️ DEPRECATED: Backend modules in ext_tables.php"
|
||||
echo " → Migrate to Configuration/Backend/Modules.php"
|
||||
fi
|
||||
|
||||
# Check modern files presence
|
||||
echo ""
|
||||
echo "=== Modern Configuration Files ===" [ -d "Configuration/Sets" ] && echo "✅ Configuration/Sets/ present" || echo "⚠️ Consider adding Site sets"
|
||||
[ -f "Configuration/user.tsconfig" ] && echo "✅ Configuration/user.tsconfig present"
|
||||
[ -f "Configuration/page.tsconfig" ] && echo "✅ Configuration/page.tsconfig present"
|
||||
[ -f "Configuration/Backend/Modules.php" ] && echo "✅ Configuration/Backend/Modules.php present"
|
||||
|
||||
echo ""
|
||||
echo "Deprecation check complete"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference Matrix
|
||||
|
||||
| Old Approach | Modern Approach (v13) | Status |
|
||||
|--------------|----------------------|--------|
|
||||
| ext_typoscript_constants.typoscript | Configuration/Sets/*/settings.definitions.yaml | Deprecated v13.1 |
|
||||
| ext_typoscript_setup.typoscript | Configuration/Sets/*/setup.typoscript | Deprecated v13.1 |
|
||||
| addUserTSConfig() in ext_localconf.php | Configuration/user.tsconfig | Removal in v14 |
|
||||
| Page TSconfig in ext_localconf.php | Configuration/page.tsconfig | Modern v12+ |
|
||||
| registerModule() in ext_tables.php | Configuration/Backend/Modules.php | Modern v13+ |
|
||||
| Static files in ext_tables.php | Configuration/TCA/Overrides/sys_template.php | Modern |
|
||||
| TCA in ext_tables.php | Configuration/TCA/*.php | Modern |
|
||||
150
skills/typo3-conformance/references/version-requirements.md
Normal file
150
skills/typo3-conformance/references/version-requirements.md
Normal file
@@ -0,0 +1,150 @@
|
||||
# TYPO3 and PHP Version Requirements
|
||||
|
||||
**Purpose:** Definitive version compatibility matrix for TYPO3 conformance checking
|
||||
**Last Updated:** 2025-01-18
|
||||
|
||||
## Official Version Support Matrix
|
||||
|
||||
### TYPO3 12 LTS
|
||||
|
||||
**Release:** April 2022
|
||||
**End of Life:** October 2026
|
||||
**PHP Support:** 8.1 - 8.4
|
||||
|
||||
| PHP Version | Support Status | Since TYPO3 Version |
|
||||
|------------|----------------|---------------------|
|
||||
| 8.1 | ✅ Supported | 12.0.0 |
|
||||
| 8.2 | ✅ Supported | 12.1.0 |
|
||||
| 8.3 | ✅ Supported | 12.4.0 |
|
||||
| 8.4 | ✅ Supported | 12.4.24 (Dec 2024) |
|
||||
|
||||
**Minimum Requirements:**
|
||||
- PHP: 8.1.0
|
||||
- Database: MariaDB 10.4+ / MySQL 8.0+ / PostgreSQL 10.0+ / SQLite 3.8.3+
|
||||
|
||||
### TYPO3 13 LTS
|
||||
|
||||
**Release:** October 2024
|
||||
**End of Life:** April 2028
|
||||
**PHP Support:** 8.2 - 8.4
|
||||
|
||||
| PHP Version | Support Status | Since TYPO3 Version |
|
||||
|------------|----------------|---------------------|
|
||||
| 8.1 | ❌ Not Supported | - |
|
||||
| 8.2 | ✅ Supported | 13.0.0 |
|
||||
| 8.3 | ✅ Supported | 13.0.0 |
|
||||
| 8.4 | ✅ Supported | 13.4.0 |
|
||||
|
||||
**Minimum Requirements:**
|
||||
- PHP: 8.2.0
|
||||
- Database: MariaDB 10.4+ / MySQL 8.0+ / PostgreSQL 10.0+ / SQLite 3.8.3+
|
||||
|
||||
## Conformance Checker Standards
|
||||
|
||||
The TYPO3 conformance checker validates extensions against:
|
||||
|
||||
**Target Versions:**
|
||||
- TYPO3: 12.4 LTS / 13.x
|
||||
- PHP: 8.1 / 8.2 / 8.3 / 8.4
|
||||
- PSR Standards: PSR-11 (DI), PSR-12 (Coding Style), PSR-14 (Events), PSR-15 (Middleware)
|
||||
|
||||
**Why This Range:**
|
||||
- Covers both TYPO3 12 LTS and 13 LTS
|
||||
- PHP 8.1+ ensures support for all modern PHP features used in TYPO3 extensions
|
||||
- Extensions can target TYPO3 12 (PHP 8.1+) and/or TYPO3 13 (PHP 8.2+)
|
||||
|
||||
## Extension composer.json Examples
|
||||
|
||||
### TYPO3 12 LTS Only
|
||||
```json
|
||||
{
|
||||
"require": {
|
||||
"php": "^8.1 || ^8.2 || ^8.3 || ^8.4",
|
||||
"typo3/cms-core": "^12.4"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### TYPO3 13 LTS Only
|
||||
```json
|
||||
{
|
||||
"require": {
|
||||
"php": "^8.2 || ^8.3 || ^8.4",
|
||||
"typo3/cms-core": "^13.4"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### TYPO3 12 and 13 LTS (Recommended for New Extensions)
|
||||
```json
|
||||
{
|
||||
"require": {
|
||||
"php": "^8.2 || ^8.3 || ^8.4",
|
||||
"typo3/cms-core": "^12.4 || ^13.4"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** When targeting both TYPO3 12 and 13, use PHP 8.2+ as minimum to satisfy TYPO3 13's requirements.
|
||||
|
||||
## PHP Feature Availability
|
||||
|
||||
### PHP 8.1 Features (TYPO3 12+)
|
||||
- Enumerations
|
||||
- Readonly properties
|
||||
- First-class callable syntax
|
||||
- New in initializers
|
||||
- Pure intersection types
|
||||
- Never return type
|
||||
- Final class constants
|
||||
- Fibers
|
||||
|
||||
### PHP 8.2 Features (TYPO3 13+)
|
||||
- Readonly classes
|
||||
- Disjunctive Normal Form (DNF) types
|
||||
- Null, false, and true as standalone types
|
||||
- Constants in traits
|
||||
- Deprecated dynamic properties
|
||||
|
||||
### PHP 8.3 Features (TYPO3 12.4+ / 13+)
|
||||
- Typed class constants
|
||||
- Dynamic class constant fetch
|
||||
- `#[\Override]` attribute
|
||||
- `json_validate()` function
|
||||
|
||||
### PHP 8.4 Features (TYPO3 12.4.24+ / 13.4+)
|
||||
- Property hooks
|
||||
- Asymmetric visibility
|
||||
- New array functions
|
||||
- HTML5 support in DOM extension
|
||||
|
||||
## Migration Paths
|
||||
|
||||
### From TYPO3 11 to 12
|
||||
1. Update PHP to 8.1+ (recommended: 8.2+)
|
||||
2. Update extension to TYPO3 12 compatibility
|
||||
3. Test thoroughly on PHP 8.2+ for future TYPO3 13 compatibility
|
||||
|
||||
### From TYPO3 12 to 13
|
||||
1. Ensure PHP 8.2+ is already in use
|
||||
2. Update TYPO3 dependencies to ^13.4
|
||||
3. Remove deprecated API usage
|
||||
4. Update Services.yaml for TYPO3 13 changes (if any)
|
||||
|
||||
## Deprecation Timeline
|
||||
|
||||
**PHP Versions:**
|
||||
- PHP 8.0: End of Life - November 2023 (Not supported by TYPO3 12/13)
|
||||
- PHP 8.1: Security fixes until November 2025
|
||||
- PHP 8.2: Security fixes until December 2026
|
||||
- PHP 8.3: Security fixes until December 2027
|
||||
- PHP 8.4: Security fixes until December 2028
|
||||
|
||||
**Recommendation:** Target PHP 8.2+ for new extensions to ensure long-term support alignment with TYPO3 13 LTS lifecycle.
|
||||
|
||||
## References
|
||||
|
||||
- [TYPO3 12 System Requirements](https://docs.typo3.org/m/typo3/reference-coreapi/12.4/en-us/Installation/Index.html)
|
||||
- [TYPO3 13 System Requirements](https://docs.typo3.org/m/typo3/reference-coreapi/13.4/en-us/Administration/Installation/SystemRequirements/Index.html)
|
||||
- [PHP Release Cycles](https://www.php.net/supported-versions.php)
|
||||
- [TYPO3 Roadmap](https://typo3.org/cms/roadmap)
|
||||
180
skills/typo3-conformance/scripts/check-architecture.sh
Executable file
180
skills/typo3-conformance/scripts/check-architecture.sh
Executable file
@@ -0,0 +1,180 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#
|
||||
# TYPO3 PHP Architecture Conformance Checker
|
||||
#
|
||||
# Validates dependency injection, services, events, and architectural patterns
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
PROJECT_DIR="${1:-.}"
|
||||
cd "${PROJECT_DIR}"
|
||||
|
||||
echo "## 3. PHP Architecture Conformance"
|
||||
echo ""
|
||||
|
||||
has_issues=0
|
||||
|
||||
### Check for Services.yaml
|
||||
echo "### Dependency Injection Configuration"
|
||||
echo ""
|
||||
|
||||
if [ -f "Configuration/Services.yaml" ]; then
|
||||
echo "- ✅ Configuration/Services.yaml present"
|
||||
|
||||
# Check if it has basic DI configuration
|
||||
if grep -q "autowire: true" Configuration/Services.yaml; then
|
||||
echo " - ✅ Autowiring enabled"
|
||||
else
|
||||
echo " - ⚠️ Autowiring not enabled"
|
||||
fi
|
||||
|
||||
if grep -q "autoconfigure: true" Configuration/Services.yaml; then
|
||||
echo " - ✅ Autoconfiguration enabled"
|
||||
else
|
||||
echo " - ⚠️ Autoconfiguration not enabled"
|
||||
fi
|
||||
else
|
||||
echo "- ❌ Configuration/Services.yaml missing (CRITICAL)"
|
||||
has_issues=1
|
||||
fi
|
||||
|
||||
### Check for deprecated patterns
|
||||
echo ""
|
||||
echo "### Deprecated Pattern Detection"
|
||||
echo ""
|
||||
|
||||
# Check for GeneralUtility::makeInstance
|
||||
makeinstance_count=$(grep -r "GeneralUtility::makeInstance" Classes/ 2>/dev/null | wc -l)
|
||||
if [ $makeinstance_count -eq 0 ]; then
|
||||
echo "- ✅ No GeneralUtility::makeInstance() usage found"
|
||||
else
|
||||
echo "- ❌ ${makeinstance_count} instances of GeneralUtility::makeInstance() found"
|
||||
echo " - Should use constructor injection instead"
|
||||
has_issues=1
|
||||
fi
|
||||
|
||||
# Check for global state access
|
||||
globals_count=$(grep -r '\$GLOBALS\[' Classes/ 2>/dev/null | wc -l)
|
||||
if [ $globals_count -eq 0 ]; then
|
||||
echo "- ✅ No \$GLOBALS access found"
|
||||
else
|
||||
echo "- ❌ ${globals_count} instances of \$GLOBALS access found"
|
||||
echo " - Should use dependency injection instead"
|
||||
has_issues=1
|
||||
fi
|
||||
|
||||
### Check for constructor injection
|
||||
echo ""
|
||||
echo "### Dependency Injection Patterns"
|
||||
echo ""
|
||||
|
||||
# Check for constructors with dependencies
|
||||
constructors=$(grep -r "public function __construct" Classes/ 2>/dev/null | wc -l)
|
||||
if [ $constructors -gt 0 ]; then
|
||||
echo "- ✅ ${constructors} classes use constructors (potential DI)"
|
||||
else
|
||||
echo "- ⚠️ No constructor injection found"
|
||||
fi
|
||||
|
||||
# Check for method injection (inject* methods)
|
||||
inject_methods=$(grep -r "public function inject[A-Z]" Classes/ 2>/dev/null | wc -l)
|
||||
if [ $inject_methods -gt 0 ]; then
|
||||
echo "- ⚠️ ${inject_methods} method injection patterns found (inject*)"
|
||||
echo " - Consider using constructor injection instead (more modern)"
|
||||
fi
|
||||
|
||||
### Check for PSR-14 events
|
||||
echo ""
|
||||
echo "### Event System"
|
||||
echo ""
|
||||
|
||||
# Check for event classes
|
||||
event_classes=$(find Classes/ -type d -name "Event" 2>/dev/null || echo "")
|
||||
if [ -n "$event_classes" ]; then
|
||||
event_count=$(find Classes/ -path "*/Event/*.php" 2>/dev/null | wc -l)
|
||||
echo "- ✅ ${event_count} event classes found in Classes/Event/"
|
||||
else
|
||||
echo "- ⚠️ No Classes/Event/ directory found"
|
||||
fi
|
||||
|
||||
# Check for event listeners
|
||||
listener_classes=$(find Classes/ -type d -name "EventListener" 2>/dev/null || echo "")
|
||||
if [ -n "$listener_classes" ]; then
|
||||
listener_count=$(find Classes/ -path "*/EventListener/*.php" 2>/dev/null | wc -l)
|
||||
echo "- ✅ ${listener_count} event listeners found in Classes/EventListener/"
|
||||
else
|
||||
echo "- ⚠️ No Classes/EventListener/ directory found"
|
||||
fi
|
||||
|
||||
### Check for Extbase patterns
|
||||
echo ""
|
||||
echo "### Extbase Architecture"
|
||||
echo ""
|
||||
|
||||
# Check for domain models
|
||||
if [ -d "Classes/Domain/Model" ]; then
|
||||
model_count=$(find Classes/Domain/Model/ -name "*.php" 2>/dev/null | wc -l)
|
||||
echo "- ✅ ${model_count} domain models found"
|
||||
else
|
||||
echo "- ℹ️ No Classes/Domain/Model/ (not using Extbase models)"
|
||||
fi
|
||||
|
||||
# Check for repositories
|
||||
if [ -d "Classes/Domain/Repository" ]; then
|
||||
repo_count=$(find Classes/Domain/Repository/ -name "*.php" 2>/dev/null | wc -l)
|
||||
echo "- ✅ ${repo_count} repositories found"
|
||||
|
||||
# Check if repositories extend Repository
|
||||
proper_repos=$(grep -r "extends.*Repository" Classes/Domain/Repository/ 2>/dev/null | wc -l)
|
||||
if [ $proper_repos -gt 0 ]; then
|
||||
echo " - ✅ Repositories extend base Repository class"
|
||||
fi
|
||||
else
|
||||
echo "- ℹ️ No Classes/Domain/Repository/ (not using Extbase repositories)"
|
||||
fi
|
||||
|
||||
# Check for controllers
|
||||
if [ -d "Classes/Controller" ]; then
|
||||
controller_count=$(find Classes/Controller/ -name "*.php" 2>/dev/null | wc -l)
|
||||
echo "- ✅ ${controller_count} controllers found"
|
||||
|
||||
# Check if controllers extend ActionController
|
||||
proper_controllers=$(grep -r "extends ActionController" Classes/Controller/ 2>/dev/null | wc -l)
|
||||
if [ $proper_controllers -gt 0 ]; then
|
||||
echo " - ✅ Controllers extend ActionController"
|
||||
fi
|
||||
fi
|
||||
|
||||
### Check for PSR-15 middleware
|
||||
echo ""
|
||||
echo "### Middleware"
|
||||
echo ""
|
||||
|
||||
if [ -f "Configuration/RequestMiddlewares.php" ]; then
|
||||
echo "- ✅ Configuration/RequestMiddlewares.php present"
|
||||
|
||||
middleware_count=$(find Classes/ -path "*/Middleware/*.php" 2>/dev/null | wc -l)
|
||||
if [ $middleware_count -gt 0 ]; then
|
||||
echo " - ✅ ${middleware_count} middleware classes found"
|
||||
fi
|
||||
else
|
||||
echo "- ℹ️ No Configuration/RequestMiddlewares.php (not using custom middleware)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "### Summary"
|
||||
echo ""
|
||||
|
||||
if [ $has_issues -eq 0 ]; then
|
||||
echo "- ✅ **PHP Architecture: PASSED**"
|
||||
else
|
||||
echo "- ⚠️ **PHP Architecture: ISSUES FOUND**"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "---"
|
||||
echo ""
|
||||
|
||||
exit $has_issues
|
||||
168
skills/typo3-conformance/scripts/check-coding-standards.sh
Executable file
168
skills/typo3-conformance/scripts/check-coding-standards.sh
Executable file
@@ -0,0 +1,168 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#
|
||||
# TYPO3 Coding Standards Conformance Checker
|
||||
#
|
||||
# Validates PSR-12 compliance and TYPO3-specific code style
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
PROJECT_DIR="${1:-.}"
|
||||
cd "${PROJECT_DIR}"
|
||||
|
||||
echo "## 2. Coding Standards Conformance"
|
||||
echo ""
|
||||
|
||||
has_issues=0
|
||||
|
||||
# Find all PHP files in Classes/
|
||||
if [ ! -d "Classes" ]; then
|
||||
echo "- ❌ Classes/ directory not found"
|
||||
echo ""
|
||||
echo "---"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
php_files=$(find Classes/ -name "*.php" 2>/dev/null || echo "")
|
||||
|
||||
if [ -z "$php_files" ]; then
|
||||
echo "- ⚠️ No PHP files found in Classes/"
|
||||
echo ""
|
||||
echo "---"
|
||||
echo ""
|
||||
exit 0
|
||||
fi
|
||||
|
||||
total_files=$(echo "$php_files" | wc -l)
|
||||
echo "**Total PHP files:** $total_files"
|
||||
echo ""
|
||||
|
||||
### Check for strict types
|
||||
echo "### Strict Types Declaration"
|
||||
echo ""
|
||||
missing_strict=0
|
||||
for file in $php_files; do
|
||||
if ! grep -q "declare(strict_types=1)" "$file"; then
|
||||
missing_strict=$((missing_strict + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $missing_strict -eq 0 ]; then
|
||||
echo "- ✅ All files have declare(strict_types=1)"
|
||||
else
|
||||
echo "- ❌ ${missing_strict} files missing declare(strict_types=1)"
|
||||
has_issues=1
|
||||
fi
|
||||
|
||||
### Check for old array syntax
|
||||
echo ""
|
||||
echo "### Array Syntax"
|
||||
echo ""
|
||||
old_array_count=$(grep -r "array(" Classes/ 2>/dev/null | wc -l)
|
||||
if [ $old_array_count -eq 0 ]; then
|
||||
echo "- ✅ No old array() syntax found"
|
||||
else
|
||||
echo "- ❌ ${old_array_count} instances of old array() syntax (should use [])"
|
||||
has_issues=1
|
||||
fi
|
||||
|
||||
### Check for proper namespace
|
||||
echo ""
|
||||
echo "### Namespace Structure"
|
||||
echo ""
|
||||
files_without_namespace=0
|
||||
for file in $php_files; do
|
||||
if ! grep -q "^namespace " "$file"; then
|
||||
files_without_namespace=$((files_without_namespace + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $files_without_namespace -eq 0 ]; then
|
||||
echo "- ✅ All files have namespace declaration"
|
||||
else
|
||||
echo "- ❌ ${files_without_namespace} files missing namespace declaration"
|
||||
has_issues=1
|
||||
fi
|
||||
|
||||
### Check for PHPDoc comments on classes
|
||||
echo ""
|
||||
echo "### PHPDoc Comments"
|
||||
echo ""
|
||||
classes_without_doc=0
|
||||
for file in $php_files; do
|
||||
# Simple check: look for /** before class declaration
|
||||
if grep -q "^class " "$file" || grep -q "^final class " "$file"; then
|
||||
if ! grep -B 5 "^class \|^final class " "$file" | grep -q "/\*\*"; then
|
||||
classes_without_doc=$((classes_without_doc + 1))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $classes_without_doc -eq 0 ]; then
|
||||
echo "- ✅ All classes have PHPDoc comments"
|
||||
else
|
||||
echo "- ⚠️ ${classes_without_doc} classes missing PHPDoc comments"
|
||||
fi
|
||||
|
||||
### Check naming conventions
|
||||
echo ""
|
||||
echo "### Naming Conventions"
|
||||
echo ""
|
||||
|
||||
# Check for snake_case in class names (should be UpperCamelCase)
|
||||
snake_case_classes=$(grep -rE "^(final )?class [a-z][a-z0-9_]*" Classes/ 2>/dev/null | wc -l)
|
||||
if [ $snake_case_classes -gt 0 ]; then
|
||||
echo "- ❌ ${snake_case_classes} classes using incorrect naming (should be UpperCamelCase)"
|
||||
has_issues=1
|
||||
else
|
||||
echo "- ✅ Class naming follows UpperCamelCase convention"
|
||||
fi
|
||||
|
||||
### Check for tabs instead of spaces
|
||||
echo ""
|
||||
echo "### Indentation"
|
||||
echo ""
|
||||
files_with_tabs=0
|
||||
for file in $php_files; do
|
||||
if grep -qP "\t" "$file"; then
|
||||
files_with_tabs=$((files_with_tabs + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $files_with_tabs -eq 0 ]; then
|
||||
echo "- ✅ No tabs found (using spaces for indentation)"
|
||||
else
|
||||
echo "- ❌ ${files_with_tabs} files using tabs instead of spaces"
|
||||
has_issues=1
|
||||
fi
|
||||
|
||||
### Check for proper use statements
|
||||
echo ""
|
||||
echo "### Use Statements"
|
||||
echo ""
|
||||
|
||||
# Check if use statements are present and not duplicated
|
||||
duplicate_uses=$(grep -rh "^use " Classes/ 2>/dev/null | sort | uniq -d | wc -l)
|
||||
if [ $duplicate_uses -gt 0 ]; then
|
||||
echo "- ⚠️ ${duplicate_uses} duplicate use statements found"
|
||||
else
|
||||
echo "- ✅ No duplicate use statements"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "### Summary"
|
||||
echo ""
|
||||
|
||||
if [ $has_issues -eq 0 ]; then
|
||||
echo "- ✅ **Coding standards: PASSED**"
|
||||
else
|
||||
echo "- ⚠️ **Coding standards: ISSUES FOUND**"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "---"
|
||||
echo ""
|
||||
|
||||
exit $has_issues
|
||||
197
skills/typo3-conformance/scripts/check-conformance.sh
Executable file
197
skills/typo3-conformance/scripts/check-conformance.sh
Executable file
@@ -0,0 +1,197 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#
|
||||
# TYPO3 Extension Conformance Checker
|
||||
#
|
||||
# Main script to orchestrate all conformance checks
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Configuration
|
||||
PROJECT_DIR="${1:-.}"
|
||||
REPORT_DIR="${PROJECT_DIR}/.conformance-reports"
|
||||
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
|
||||
REPORT_FILE="${REPORT_DIR}/conformance_${TIMESTAMP}.md"
|
||||
|
||||
# Script directory
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
echo -e "${BLUE}╔════════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${BLUE}║ TYPO3 Extension Conformance Checker ║${NC}"
|
||||
echo -e "${BLUE}╚════════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
echo -e "${BLUE}Standards Compliance Check:${NC}"
|
||||
echo -e " • TYPO3 Version: ${YELLOW}12.4 LTS / 13.x${NC}"
|
||||
echo -e " • PHP Version: ${YELLOW}8.1 / 8.2 / 8.3 / 8.4${NC}"
|
||||
echo -e " • PSR Standard: ${YELLOW}PSR-12 (Extended Coding Style)${NC}"
|
||||
echo -e " • Architecture: ${YELLOW}Dependency Injection, PSR-14 Events${NC}"
|
||||
echo ""
|
||||
|
||||
# Create report directory
|
||||
mkdir -p "${REPORT_DIR}"
|
||||
|
||||
# Check if directory exists
|
||||
if [ ! -d "${PROJECT_DIR}" ]; then
|
||||
echo -e "${RED}✗ Error: Directory ${PROJECT_DIR} not found${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "${PROJECT_DIR}"
|
||||
|
||||
# Check if this is a TYPO3 extension
|
||||
if [ ! -f "composer.json" ] && [ ! -f "ext_emconf.php" ]; then
|
||||
echo -e "${RED}✗ Error: Not a TYPO3 extension (composer.json or ext_emconf.php not found)${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ TYPO3 Extension detected${NC}"
|
||||
echo ""
|
||||
|
||||
# Initialize report
|
||||
cat > "${REPORT_FILE}" <<'EOF'
|
||||
# TYPO3 Extension Conformance Report
|
||||
|
||||
**Generated:** $(date -u +"%Y-%m-%d %H:%M:%S UTC")
|
||||
**Project:** $(basename "$(pwd)")
|
||||
|
||||
## Standards Checked
|
||||
|
||||
This conformance check validates your extension against the following standards:
|
||||
|
||||
| Standard | Version/Specification |
|
||||
|----------|----------------------|
|
||||
| **TYPO3 Core** | 12.4 LTS / 13.x |
|
||||
| **PHP** | 8.1 / 8.2 / 8.3 / 8.4 |
|
||||
| **Coding Style** | PSR-12 (Extended Coding Style) |
|
||||
| **Architecture** | Dependency Injection (PSR-11), PSR-14 Events, PSR-15 Middleware |
|
||||
| **Testing** | PHPUnit 10+, TYPO3 Testing Framework |
|
||||
| **Documentation** | reStructuredText (RST), TYPO3 Documentation Standards |
|
||||
|
||||
**Reference Documentation:**
|
||||
- [TYPO3 Extension Architecture](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/)
|
||||
- [TYPO3 Coding Guidelines](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/CodingGuidelines/)
|
||||
- [PHP Architecture](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/PhpArchitecture/)
|
||||
- [Testing Standards](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/Testing/)
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
| Category | Score | Status |
|
||||
|----------|-------|--------|
|
||||
EOF
|
||||
|
||||
# Initialize scores
|
||||
total_score=0
|
||||
max_score=100
|
||||
|
||||
echo -e "${YELLOW}Running conformance checks...${NC}"
|
||||
echo ""
|
||||
|
||||
# 1. File Structure Check
|
||||
echo -e "${BLUE}[1/5] Checking file structure...${NC}"
|
||||
if bash "${SCRIPT_DIR}/check-file-structure.sh" "${PROJECT_DIR}" >> "${REPORT_FILE}"; then
|
||||
echo -e "${GREEN} ✓ File structure check complete${NC}"
|
||||
structure_score=18
|
||||
else
|
||||
echo -e "${YELLOW} ⚠ File structure issues found${NC}"
|
||||
structure_score=10
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 2. Coding Standards Check
|
||||
echo -e "${BLUE}[2/5] Checking coding standards...${NC}"
|
||||
if bash "${SCRIPT_DIR}/check-coding-standards.sh" "${PROJECT_DIR}" >> "${REPORT_FILE}"; then
|
||||
echo -e "${GREEN} ✓ Coding standards check complete${NC}"
|
||||
coding_score=18
|
||||
else
|
||||
echo -e "${YELLOW} ⚠ Coding standards issues found${NC}"
|
||||
coding_score=12
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 3. Architecture Check
|
||||
echo -e "${BLUE}[3/5] Checking PHP architecture...${NC}"
|
||||
if bash "${SCRIPT_DIR}/check-architecture.sh" "${PROJECT_DIR}" >> "${REPORT_FILE}"; then
|
||||
echo -e "${GREEN} ✓ Architecture check complete${NC}"
|
||||
arch_score=18
|
||||
else
|
||||
echo -e "${YELLOW} ⚠ Architecture issues found${NC}"
|
||||
arch_score=10
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 4. Testing Check
|
||||
echo -e "${BLUE}[4/6] Checking testing infrastructure...${NC}"
|
||||
if bash "${SCRIPT_DIR}/check-testing.sh" "${PROJECT_DIR}" >> "${REPORT_FILE}"; then
|
||||
echo -e "${GREEN} ✓ Testing check complete${NC}"
|
||||
test_score=16
|
||||
else
|
||||
echo -e "${YELLOW} ⚠ Testing issues found${NC}"
|
||||
test_score=8
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 5. PHPStan Baseline Check
|
||||
echo -e "${BLUE}[5/6] Checking PHPStan baseline hygiene...${NC}"
|
||||
if bash "${SCRIPT_DIR}/check-phpstan-baseline.sh" "${PROJECT_DIR}"; then
|
||||
echo -e "${GREEN} ✓ PHPStan baseline hygiene check passed${NC}"
|
||||
baseline_score=10
|
||||
else
|
||||
echo -e "${RED} ✗ PHPStan baseline violation detected${NC}"
|
||||
baseline_score=0
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 6. Generate comprehensive report
|
||||
echo -e "${BLUE}[6/6] Generating final report...${NC}"
|
||||
bash "${SCRIPT_DIR}/generate-report.sh" "${PROJECT_DIR}" "${REPORT_FILE}" \
|
||||
"${structure_score}" "${coding_score}" "${arch_score}" "${test_score}"
|
||||
echo ""
|
||||
|
||||
# Calculate total (including baseline hygiene score)
|
||||
total_score=$((structure_score + coding_score + arch_score + test_score + baseline_score))
|
||||
|
||||
# Display summary
|
||||
echo -e "${BLUE}╔════════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${BLUE}║ Conformance Results ║${NC}"
|
||||
echo -e "${BLUE}╚════════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
echo -e " File Structure: ${structure_score}/20"
|
||||
echo -e " Coding Standards: ${coding_score}/20"
|
||||
echo -e " PHP Architecture: ${arch_score}/20"
|
||||
echo -e " Testing Standards: ${test_score}/20"
|
||||
echo -e " Baseline Hygiene: ${baseline_score}/10"
|
||||
echo -e " Best Practices: 10/10"
|
||||
echo ""
|
||||
echo -e " ${BLUE}Total Score: ${total_score}/100${NC}"
|
||||
echo ""
|
||||
|
||||
if [ ${total_score} -ge 80 ]; then
|
||||
echo -e "${GREEN}✓ EXCELLENT conformance level${NC}"
|
||||
elif [ ${total_score} -ge 60 ]; then
|
||||
echo -e "${YELLOW}⚠ GOOD conformance level (some improvements recommended)${NC}"
|
||||
elif [ ${total_score} -ge 40 ]; then
|
||||
echo -e "${YELLOW}⚠ FAIR conformance level (several issues to address)${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ POOR conformance level (major improvements needed)${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}Report saved to: ${REPORT_FILE}${NC}"
|
||||
echo ""
|
||||
|
||||
# Exit with appropriate code
|
||||
if [ ${total_score} -ge 60 ]; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
197
skills/typo3-conformance/scripts/check-file-structure.sh
Executable file
197
skills/typo3-conformance/scripts/check-file-structure.sh
Executable file
@@ -0,0 +1,197 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#
|
||||
# TYPO3 File Structure Conformance Checker
|
||||
#
|
||||
# Validates extension directory structure and required files
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
PROJECT_DIR="${1:-.}"
|
||||
cd "${PROJECT_DIR}"
|
||||
|
||||
echo "## 1. File Structure Conformance"
|
||||
echo ""
|
||||
|
||||
# Track issues
|
||||
has_issues=0
|
||||
|
||||
echo "### Required Files"
|
||||
echo ""
|
||||
|
||||
# Check required files
|
||||
if [ -f "composer.json" ]; then
|
||||
echo "- ✅ composer.json present"
|
||||
else
|
||||
echo "- ❌ composer.json missing (CRITICAL)"
|
||||
has_issues=1
|
||||
fi
|
||||
|
||||
if [ -f "ext_emconf.php" ]; then
|
||||
echo "- ✅ ext_emconf.php present"
|
||||
else
|
||||
echo "- ⚠️ ext_emconf.php missing (required for TER publication)"
|
||||
fi
|
||||
|
||||
if [ -f "Documentation/Index.rst" ]; then
|
||||
echo "- ✅ Documentation/Index.rst present"
|
||||
else
|
||||
echo "- ⚠️ Documentation/Index.rst missing (required for docs.typo3.org)"
|
||||
fi
|
||||
|
||||
if [ -f "Documentation/Settings.cfg" ]; then
|
||||
echo "- ✅ Documentation/Settings.cfg present"
|
||||
else
|
||||
echo "- ⚠️ Documentation/Settings.cfg missing (required for docs.typo3.org)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "### Directory Structure"
|
||||
echo ""
|
||||
|
||||
# Check core directories
|
||||
if [ -d "Classes" ]; then
|
||||
echo "- ✅ Classes/ directory present"
|
||||
# Check for common subdirectories
|
||||
if [ -d "Classes/Controller" ]; then
|
||||
echo " - ✅ Classes/Controller/ found"
|
||||
fi
|
||||
if [ -d "Classes/Domain/Model" ]; then
|
||||
echo " - ✅ Classes/Domain/Model/ found"
|
||||
fi
|
||||
if [ -d "Classes/Domain/Repository" ]; then
|
||||
echo " - ✅ Classes/Domain/Repository/ found"
|
||||
fi
|
||||
else
|
||||
echo "- ❌ Classes/ directory missing (CRITICAL)"
|
||||
has_issues=1
|
||||
fi
|
||||
|
||||
if [ -d "Configuration" ]; then
|
||||
echo "- ✅ Configuration/ directory present"
|
||||
if [ -d "Configuration/TCA" ]; then
|
||||
echo " - ✅ Configuration/TCA/ found"
|
||||
fi
|
||||
if [ -f "Configuration/Services.yaml" ]; then
|
||||
echo " - ✅ Configuration/Services.yaml found"
|
||||
else
|
||||
echo " - ⚠️ Configuration/Services.yaml missing (recommended)"
|
||||
fi
|
||||
if [ -d "Configuration/Backend" ]; then
|
||||
echo " - ✅ Configuration/Backend/ found"
|
||||
fi
|
||||
else
|
||||
echo "- ⚠️ Configuration/ directory missing"
|
||||
fi
|
||||
|
||||
if [ -d "Resources" ]; then
|
||||
echo "- ✅ Resources/ directory present"
|
||||
if [ -d "Resources/Private" ] && [ -d "Resources/Public" ]; then
|
||||
echo " - ✅ Resources/Private/ and Resources/Public/ properly separated"
|
||||
else
|
||||
echo " - ⚠️ Resources/ not properly separated into Private/ and Public/"
|
||||
fi
|
||||
else
|
||||
echo "- ⚠️ Resources/ directory missing"
|
||||
fi
|
||||
|
||||
if [ -d "Tests" ]; then
|
||||
echo "- ✅ Tests/ directory present"
|
||||
if [ -d "Tests/Unit" ]; then
|
||||
echo " - ✅ Tests/Unit/ found"
|
||||
else
|
||||
echo " - ⚠️ Tests/Unit/ missing"
|
||||
fi
|
||||
if [ -d "Tests/Functional" ]; then
|
||||
echo " - ✅ Tests/Functional/ found"
|
||||
else
|
||||
echo " - ⚠️ Tests/Functional/ missing"
|
||||
fi
|
||||
else
|
||||
echo "- ⚠️ Tests/ directory missing"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "### Anti-Patterns Check"
|
||||
echo ""
|
||||
|
||||
# Check for PHP files in root (except ext_* files)
|
||||
# Show all files but distinguish between tracked (issues) and untracked (info)
|
||||
tracked_files=()
|
||||
untracked_files=()
|
||||
all_root_php_files=()
|
||||
|
||||
# Find all PHP files in root (except ext_* files)
|
||||
while IFS= read -r file; do
|
||||
filename=$(basename "$file")
|
||||
if [[ "$filename" != ext_*.php ]]; then
|
||||
all_root_php_files+=("$filename")
|
||||
fi
|
||||
done < <(find . -maxdepth 1 -name "*.php" 2>/dev/null || true)
|
||||
|
||||
# Check if files are tracked in git (if git repository exists)
|
||||
if [ -d ".git" ]; then
|
||||
for file in "${all_root_php_files[@]}"; do
|
||||
if git ls-files --error-unmatch "$file" >/dev/null 2>&1; then
|
||||
tracked_files+=("$file")
|
||||
else
|
||||
untracked_files+=("$file")
|
||||
fi
|
||||
done
|
||||
else
|
||||
# No git repository - treat all files as tracked (potential issues)
|
||||
tracked_files=("${all_root_php_files[@]}")
|
||||
fi
|
||||
|
||||
# Report tracked files (these are issues)
|
||||
if [ ${#tracked_files[@]} -gt 0 ]; then
|
||||
if [ -d ".git" ]; then
|
||||
echo "- ❌ ${#tracked_files[@]} PHP file(s) in root directory committed to repository:"
|
||||
else
|
||||
echo "- ❌ ${#tracked_files[@]} PHP file(s) found in root directory:"
|
||||
fi
|
||||
for file in "${tracked_files[@]}"; do
|
||||
echo " - ${file} (ISSUE: should be in Classes/ or Build/)"
|
||||
done
|
||||
has_issues=1
|
||||
fi
|
||||
|
||||
# Report untracked files (informational only)
|
||||
if [ ${#untracked_files[@]} -gt 0 ]; then
|
||||
echo "- ℹ️ ${#untracked_files[@]} untracked PHP file(s) in root (ignored, not committed):"
|
||||
for file in "${untracked_files[@]}"; do
|
||||
echo " - ${file} (local file, not in repository)"
|
||||
done
|
||||
fi
|
||||
|
||||
# Success message if no files found
|
||||
if [ ${#all_root_php_files[@]} -eq 0 ]; then
|
||||
echo "- ✅ No PHP files in root (except ext_* files)"
|
||||
fi
|
||||
|
||||
# Check for deprecated ext_tables.php
|
||||
if [ -f "ext_tables.php" ]; then
|
||||
echo "- ⚠️ ext_tables.php present (consider migrating to Configuration/Backend/)"
|
||||
fi
|
||||
|
||||
# Check for wrong directory naming
|
||||
if [ -d "Classes/Controllers" ]; then
|
||||
echo "- ❌ Classes/Controllers/ found (should be Controller/ singular)"
|
||||
has_issues=1
|
||||
fi
|
||||
|
||||
if [ -d "Classes/Helpers" ]; then
|
||||
echo "- ⚠️ Classes/Helpers/ found (should use Utility/ instead)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "---"
|
||||
echo ""
|
||||
|
||||
# Return appropriate exit code
|
||||
if [ ${has_issues} -eq 0 ]; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
110
skills/typo3-conformance/scripts/check-phpstan-baseline.sh
Executable file
110
skills/typo3-conformance/scripts/check-phpstan-baseline.sh
Executable file
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# TYPO3 Extension Conformance Checker - PHPStan Baseline Validation
|
||||
# Verifies that new code does not add errors to phpstan-baseline.neon
|
||||
#
|
||||
# Usage:
|
||||
# ./check-phpstan-baseline.sh [path-to-extension]
|
||||
#
|
||||
# Returns:
|
||||
# 0 = No baseline additions detected
|
||||
# 1 = New errors added to baseline (violation)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Parse arguments
|
||||
PROJECT_ROOT="${1:-.}"
|
||||
cd "$PROJECT_ROOT" || exit 1
|
||||
|
||||
echo "Checking PHPStan baseline hygiene in: $PROJECT_ROOT"
|
||||
echo
|
||||
|
||||
# Check if git repository
|
||||
if [ ! -d ".git" ]; then
|
||||
echo -e "${YELLOW}⚠️ Not a git repository - skipping baseline check${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Find baseline file
|
||||
BASELINE_FILE=""
|
||||
for path in "Build/phpstan-baseline.neon" "phpstan-baseline.neon" ".phpstan/baseline.neon"; do
|
||||
if [ -f "$path" ]; then
|
||||
BASELINE_FILE="$path"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$BASELINE_FILE" ]; then
|
||||
echo -e "${GREEN}✅ No baseline file found - all code passes PHPStan level 10!${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Found baseline file: $BASELINE_FILE"
|
||||
echo
|
||||
|
||||
# Check if baseline is modified in current changes
|
||||
if ! git diff --quiet "$BASELINE_FILE" 2>/dev/null; then
|
||||
echo -e "${YELLOW}⚠️ Baseline file has uncommitted changes${NC}"
|
||||
echo
|
||||
|
||||
# Extract error counts from diff
|
||||
BEFORE_COUNT=$(git show "HEAD:$BASELINE_FILE" 2>/dev/null | grep -E "^\s+count:\s+[0-9]+" | head -1 | grep -oE "[0-9]+" || echo "0")
|
||||
AFTER_COUNT=$(grep -E "^\s+count:\s+[0-9]+" "$BASELINE_FILE" | head -1 | grep -oE "[0-9]+" || echo "0")
|
||||
|
||||
if [ "$AFTER_COUNT" -gt "$BEFORE_COUNT" ]; then
|
||||
ADDED=$((AFTER_COUNT - BEFORE_COUNT))
|
||||
echo -e "${RED}❌ BASELINE VIOLATION DETECTED${NC}"
|
||||
echo
|
||||
echo "Error count increased: $BEFORE_COUNT → $AFTER_COUNT (+$ADDED errors)"
|
||||
echo
|
||||
echo "New code added $ADDED errors to the baseline!"
|
||||
echo
|
||||
echo "The baseline exists only for legacy code."
|
||||
echo "All new code MUST pass PHPStan level 10 without baseline suppression."
|
||||
echo
|
||||
echo -e "${YELLOW}How to fix:${NC}"
|
||||
echo "1. Run: composer ci:php:stan"
|
||||
echo "2. Review the new errors reported"
|
||||
echo "3. Fix the underlying issues (see coding-guidelines.md for patterns)"
|
||||
echo "4. Revert baseline changes: git checkout $BASELINE_FILE"
|
||||
echo "5. Verify: composer ci:php:stan should pass with original baseline"
|
||||
echo
|
||||
exit 1
|
||||
elif [ "$AFTER_COUNT" -lt "$BEFORE_COUNT" ]; then
|
||||
REMOVED=$((BEFORE_COUNT - AFTER_COUNT))
|
||||
echo -e "${GREEN}✅ Excellent! Baseline reduced by $REMOVED errors${NC}"
|
||||
echo
|
||||
echo "You fixed existing baseline issues - great work!"
|
||||
echo
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ Baseline modified but count unchanged${NC}"
|
||||
echo
|
||||
echo "Review the baseline diff to ensure changes are intentional:"
|
||||
echo " git diff $BASELINE_FILE"
|
||||
echo
|
||||
fi
|
||||
else
|
||||
echo -e "${GREEN}✅ No changes to baseline file${NC}"
|
||||
fi
|
||||
|
||||
# Check for baseline in staged changes
|
||||
if git diff --cached --quiet "$BASELINE_FILE" 2>/dev/null; then
|
||||
echo -e "${GREEN}✅ No baseline changes staged for commit${NC}"
|
||||
else
|
||||
echo
|
||||
echo -e "${YELLOW}⚠️ Warning: Baseline file is staged for commit${NC}"
|
||||
echo
|
||||
echo "Review staged baseline changes:"
|
||||
echo " git diff --cached $BASELINE_FILE"
|
||||
echo
|
||||
fi
|
||||
|
||||
echo
|
||||
echo -e "${GREEN}✅ PHPStan baseline hygiene check passed${NC}"
|
||||
exit 0
|
||||
199
skills/typo3-conformance/scripts/check-testing.sh
Executable file
199
skills/typo3-conformance/scripts/check-testing.sh
Executable file
@@ -0,0 +1,199 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#
|
||||
# TYPO3 Testing Standards Conformance Checker
|
||||
#
|
||||
# Validates testing infrastructure and test coverage
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
PROJECT_DIR="${1:-.}"
|
||||
cd "${PROJECT_DIR}"
|
||||
|
||||
echo "## 4. Testing Standards Conformance"
|
||||
echo ""
|
||||
|
||||
has_issues=0
|
||||
|
||||
### Check for Tests directory
|
||||
echo "### Test Infrastructure"
|
||||
echo ""
|
||||
|
||||
if [ ! -d "Tests" ]; then
|
||||
echo "- ❌ Tests/ directory missing (CRITICAL)"
|
||||
has_issues=1
|
||||
echo ""
|
||||
echo "---"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "- ✅ Tests/ directory present"
|
||||
|
||||
### Check for PHPUnit configuration
|
||||
echo ""
|
||||
echo "### PHPUnit Configuration"
|
||||
echo ""
|
||||
|
||||
if [ -f "Build/phpunit/UnitTests.xml" ] || [ -f "phpunit.xml" ]; then
|
||||
echo "- ✅ Unit test configuration found"
|
||||
else
|
||||
echo "- ❌ No Unit test configuration (Build/phpunit/UnitTests.xml or phpunit.xml)"
|
||||
has_issues=1
|
||||
fi
|
||||
|
||||
if [ -f "Build/phpunit/FunctionalTests.xml" ]; then
|
||||
echo "- ✅ Functional test configuration found"
|
||||
else
|
||||
echo "- ⚠️ No Functional test configuration (Build/phpunit/FunctionalTests.xml)"
|
||||
fi
|
||||
|
||||
### Unit Tests
|
||||
echo ""
|
||||
echo "### Unit Tests"
|
||||
echo ""
|
||||
|
||||
if [ -d "Tests/Unit" ]; then
|
||||
unit_test_count=$(find Tests/Unit/ -name "*Test.php" 2>/dev/null | wc -l)
|
||||
echo "- ✅ Tests/Unit/ directory present"
|
||||
echo " - **${unit_test_count} unit test files found**"
|
||||
|
||||
if [ $unit_test_count -eq 0 ]; then
|
||||
echo " - ⚠️ No unit tests found"
|
||||
fi
|
||||
|
||||
# Check if tests mirror Classes structure
|
||||
if [ -d "Classes/Controller" ] && [ ! -d "Tests/Unit/Controller" ]; then
|
||||
echo " - ⚠️ Tests/Unit/Controller/ missing (Classes/Controller/ exists)"
|
||||
fi
|
||||
|
||||
if [ -d "Classes/Service" ] && [ ! -d "Tests/Unit/Service" ]; then
|
||||
echo " - ⚠️ Tests/Unit/Service/ missing (Classes/Service/ exists)"
|
||||
fi
|
||||
|
||||
if [ -d "Classes/Domain/Repository" ] && [ ! -d "Tests/Unit/Domain/Repository" ]; then
|
||||
echo " - ⚠️ Tests/Unit/Domain/Repository/ missing (Classes/Domain/Repository/ exists)"
|
||||
fi
|
||||
else
|
||||
echo "- ❌ Tests/Unit/ directory missing"
|
||||
has_issues=1
|
||||
fi
|
||||
|
||||
### Functional Tests
|
||||
echo ""
|
||||
echo "### Functional Tests"
|
||||
echo ""
|
||||
|
||||
if [ -d "Tests/Functional" ]; then
|
||||
func_test_count=$(find Tests/Functional/ -name "*Test.php" 2>/dev/null | wc -l)
|
||||
echo "- ✅ Tests/Functional/ directory present"
|
||||
echo " - **${func_test_count} functional test files found**"
|
||||
|
||||
# Check for fixtures
|
||||
if [ -d "Tests/Functional/Fixtures" ]; then
|
||||
fixture_count=$(find Tests/Functional/Fixtures/ -name "*.csv" -o -name "*.xml" 2>/dev/null | wc -l)
|
||||
echo " - ✅ Tests/Functional/Fixtures/ found (${fixture_count} fixture files)"
|
||||
else
|
||||
if [ $func_test_count -gt 0 ]; then
|
||||
echo " - ⚠️ No Tests/Functional/Fixtures/ (functional tests may need fixtures)"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "- ⚠️ Tests/Functional/ directory missing"
|
||||
echo " - Functional tests recommended for repository and database operations"
|
||||
fi
|
||||
|
||||
### Acceptance Tests
|
||||
echo ""
|
||||
echo "### Acceptance Tests"
|
||||
echo ""
|
||||
|
||||
if [ -d "Tests/Acceptance" ]; then
|
||||
accept_test_count=$(find Tests/Acceptance/ -name "*Cest.php" 2>/dev/null | wc -l)
|
||||
echo "- ✅ Tests/Acceptance/ directory present"
|
||||
echo " - **${accept_test_count} acceptance test files found**"
|
||||
|
||||
if [ -f "Tests/codeception.yml" ]; then
|
||||
echo " - ✅ codeception.yml configuration found"
|
||||
else
|
||||
echo " - ⚠️ codeception.yml configuration missing"
|
||||
fi
|
||||
else
|
||||
echo "- ℹ️ Tests/Acceptance/ not found (optional for most extensions)"
|
||||
fi
|
||||
|
||||
### Test Coverage Estimate
|
||||
echo ""
|
||||
echo "### Test Coverage Estimate"
|
||||
echo ""
|
||||
|
||||
if [ -d "Classes" ]; then
|
||||
class_count=$(find Classes/ -name "*.php" 2>/dev/null | wc -l)
|
||||
|
||||
if [ -d "Tests/Unit" ]; then
|
||||
unit_count=$(find Tests/Unit/ -name "*Test.php" 2>/dev/null | wc -l)
|
||||
else
|
||||
unit_count=0
|
||||
fi
|
||||
|
||||
if [ -d "Tests/Functional" ]; then
|
||||
func_count=$(find Tests/Functional/ -name "*Test.php" 2>/dev/null | wc -l)
|
||||
else
|
||||
func_count=0
|
||||
fi
|
||||
|
||||
total_tests=$((unit_count + func_count))
|
||||
|
||||
echo "- **Total Classes:** $class_count"
|
||||
echo "- **Total Tests:** $total_tests"
|
||||
|
||||
if [ $class_count -gt 0 ]; then
|
||||
coverage_ratio=$((total_tests * 100 / class_count))
|
||||
echo "- **Test Ratio:** ${coverage_ratio}%"
|
||||
|
||||
if [ $coverage_ratio -ge 70 ]; then
|
||||
echo " - ✅ Good test coverage (≥70%)"
|
||||
elif [ $coverage_ratio -ge 50 ]; then
|
||||
echo " - ⚠️ Moderate test coverage (50-70%)"
|
||||
else
|
||||
echo " - ❌ Low test coverage (<50%)"
|
||||
has_issues=1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
### Check for testing framework dependency
|
||||
echo ""
|
||||
echo "### Testing Framework Dependency"
|
||||
echo ""
|
||||
|
||||
if [ -f "composer.json" ]; then
|
||||
if grep -q "typo3/testing-framework" composer.json; then
|
||||
echo "- ✅ typo3/testing-framework in composer.json"
|
||||
else
|
||||
echo "- ⚠️ typo3/testing-framework not found in composer.json"
|
||||
fi
|
||||
|
||||
if grep -q "phpunit/phpunit" composer.json; then
|
||||
echo "- ✅ phpunit/phpunit in composer.json"
|
||||
else
|
||||
echo "- ⚠️ phpunit/phpunit not found in composer.json"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "### Summary"
|
||||
echo ""
|
||||
|
||||
if [ $has_issues -eq 0 ]; then
|
||||
echo "- ✅ **Testing Standards: PASSED**"
|
||||
else
|
||||
echo "- ⚠️ **Testing Standards: ISSUES FOUND**"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "---"
|
||||
echo ""
|
||||
|
||||
exit $has_issues
|
||||
143
skills/typo3-conformance/scripts/generate-report.sh
Executable file
143
skills/typo3-conformance/scripts/generate-report.sh
Executable file
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#
|
||||
# Generate final conformance report with recommendations
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
PROJECT_DIR="${1}"
|
||||
REPORT_FILE="${2}"
|
||||
STRUCTURE_SCORE="${3:-15}"
|
||||
CODING_SCORE="${4:-15}"
|
||||
ARCH_SCORE="${5:-15}"
|
||||
TEST_SCORE="${6:-15}"
|
||||
|
||||
cd "${PROJECT_DIR}"
|
||||
|
||||
# Calculate total
|
||||
TOTAL_SCORE=$((STRUCTURE_SCORE + CODING_SCORE + ARCH_SCORE + TEST_SCORE + 10))
|
||||
|
||||
# Update summary table
|
||||
sed -i "s/| Extension Architecture .*/| Extension Architecture | ${STRUCTURE_SCORE}\/20 | $(if [ ${STRUCTURE_SCORE} -ge 15 ]; then echo "✅ Passed"; else echo "⚠️ Issues"; fi) |/" "${REPORT_FILE}"
|
||||
sed -i "/| Extension Architecture /a | Coding Guidelines | ${CODING_SCORE}/20 | $(if [ ${CODING_SCORE} -ge 15 ]; then echo "✅ Passed"; else echo "⚠️ Issues"; fi) |" "${REPORT_FILE}" 2>/dev/null || true
|
||||
sed -i "/| Coding Guidelines /a | PHP Architecture | ${ARCH_SCORE}/20 | $(if [ ${ARCH_SCORE} -ge 15 ]; then echo "✅ Passed"; else echo "⚠️ Issues"; fi) |" "${REPORT_FILE}" 2>/dev/null || true
|
||||
sed -i "/| PHP Architecture /a | Testing Standards | ${TEST_SCORE}/20 | $(if [ ${TEST_SCORE} -ge 15 ]; then echo "✅ Passed"; else echo "⚠️ Issues"; fi) |" "${REPORT_FILE}" 2>/dev/null || true
|
||||
sed -i "/| Testing Standards /a | Best Practices | 10/20 | ℹ️ Partial |" "${REPORT_FILE}" 2>/dev/null || true
|
||||
sed -i "/| Best Practices /a | **TOTAL** | **${TOTAL_SCORE}/100** | $(if [ ${TOTAL_SCORE} -ge 80 ]; then echo "✅ Excellent"; elif [ ${TOTAL_SCORE} -ge 60 ]; then echo "✅ Good"; else echo "⚠️ Fair"; fi) |" "${REPORT_FILE}" 2>/dev/null || true
|
||||
|
||||
# Add final sections
|
||||
cat >> "${REPORT_FILE}" <<EOF
|
||||
|
||||
---
|
||||
|
||||
## 5. Best Practices Assessment
|
||||
|
||||
### Project Infrastructure
|
||||
- **README.md:** $(if [ -f "README.md" ]; then echo "✅ Present"; else echo "❌ Missing"; fi)
|
||||
- **LICENSE:** $(if [ -f "LICENSE" ]; then echo "✅ Present"; else echo "❌ Missing"; fi)
|
||||
- **.editorconfig:** $(if [ -f ".editorconfig" ]; then echo "✅ Present"; else echo "⚠️ Missing"; fi)
|
||||
- **.gitignore:** $(if [ -f ".gitignore" ]; then echo "✅ Present"; else echo "⚠️ Missing"; fi)
|
||||
|
||||
### Code Quality Tools
|
||||
- **php-cs-fixer:** $(if [ -f ".php-cs-fixer.dist.php" ] || [ -f ".php-cs-fixer.php" ] || [ -f "Build/.php-cs-fixer.dist.php" ] || [ -f "Build/.php-cs-fixer.php" ]; then echo "✅ Configured"; else echo "⚠️ Not configured"; fi)
|
||||
- **phpstan:** $(if [ -f "phpstan.neon" ] || [ -f "phpstan.neon.dist" ] || [ -f "Build/phpstan.neon" ] || [ -f "Build/phpstan.neon.dist" ]; then echo "✅ Configured"; else echo "⚠️ Not configured"; fi)
|
||||
- **rector:** $(if [ -f "rector.php" ] || [ -f "Build/rector.php" ]; then echo "✅ Configured"; else echo "ℹ️ Not configured"; fi)
|
||||
|
||||
### CI/CD Pipeline
|
||||
- **GitHub Actions:** $(if [ -d ".github/workflows" ]; then echo "✅ Configured"; else echo "⚠️ Not found"; fi)
|
||||
- **GitLab CI:** $(if [ -f ".gitlab-ci.yml" ]; then echo "✅ Configured"; else echo "ℹ️ Not found"; fi)
|
||||
|
||||
---
|
||||
|
||||
## Overall Assessment
|
||||
|
||||
**Total Score: ${TOTAL_SCORE}/100**
|
||||
|
||||
$(if [ ${TOTAL_SCORE} -ge 80 ]; then
|
||||
cat <<END
|
||||
### ✅ EXCELLENT Conformance Level
|
||||
|
||||
Your TYPO3 extension demonstrates strong adherence to official standards and best practices.
|
||||
|
||||
**Strengths:**
|
||||
- Well-structured architecture following TYPO3 conventions
|
||||
- Modern PHP patterns with dependency injection
|
||||
- Good code quality and testing coverage
|
||||
- Proper documentation and infrastructure
|
||||
|
||||
**Minor Improvements:**
|
||||
- Continue maintaining high standards
|
||||
- Keep dependencies updated
|
||||
- Monitor code coverage trends
|
||||
END
|
||||
elif [ ${TOTAL_SCORE} -ge 60 ]; then
|
||||
cat <<END
|
||||
### ✅ GOOD Conformance Level
|
||||
|
||||
Your TYPO3 extension follows most standards with some areas for improvement.
|
||||
|
||||
**Next Steps:**
|
||||
1. Address critical issues identified above
|
||||
2. Improve test coverage
|
||||
3. Add missing configuration files
|
||||
4. Update deprecated patterns
|
||||
|
||||
**Timeline:** 2-4 weeks for improvements
|
||||
END
|
||||
else
|
||||
cat <<END
|
||||
### ⚠️ FAIR Conformance Level
|
||||
|
||||
Your TYPO3 extension requires significant improvements to meet TYPO3 standards.
|
||||
|
||||
**Priority Actions:**
|
||||
1. Fix critical file structure issues
|
||||
2. Migrate deprecated patterns (GeneralUtility::makeInstance, \$GLOBALS)
|
||||
3. Add comprehensive testing infrastructure
|
||||
4. Improve code quality (strict types, PHPDoc, PSR-12)
|
||||
5. Add project infrastructure (CI/CD, quality tools)
|
||||
|
||||
**Timeline:** 4-8 weeks for comprehensive improvements
|
||||
END
|
||||
fi)
|
||||
|
||||
---
|
||||
|
||||
## Quick Action Checklist
|
||||
|
||||
### High Priority (Fix Now)
|
||||
$(if [ ${STRUCTURE_SCORE} -lt 15 ]; then echo "- [ ] Fix critical file structure issues (missing required files/directories)"; fi)
|
||||
$(if grep -q "GeneralUtility::makeInstance" Classes/ 2>/dev/null; then echo "- [ ] Migrate GeneralUtility::makeInstance to constructor injection"; fi)
|
||||
$(if grep -q '\$GLOBALS\[' Classes/ 2>/dev/null; then echo "- [ ] Remove \$GLOBALS access, use dependency injection"; fi)
|
||||
$(if [ ! -f "Configuration/Services.yaml" ]; then echo "- [ ] Add Configuration/Services.yaml with DI configuration"; fi)
|
||||
|
||||
### Medium Priority (Fix Soon)
|
||||
$(if [ ${CODING_SCORE} -lt 15 ]; then echo "- [ ] Add declare(strict_types=1) to all PHP files"; fi)
|
||||
$(if [ ${CODING_SCORE} -lt 15 ]; then echo "- [ ] Replace array() with [] short syntax"; fi)
|
||||
$(if [ ${TEST_SCORE} -lt 15 ]; then echo "- [ ] Add unit tests for untested classes"; fi)
|
||||
$(if [ ! -d "Tests/Functional" ]; then echo "- [ ] Add functional tests for repositories"; fi)
|
||||
|
||||
### Low Priority (Improve When Possible)
|
||||
$(if [ ! -f ".php-cs-fixer.dist.php" ] && [ ! -f ".php-cs-fixer.php" ] && [ ! -f "Build/.php-cs-fixer.dist.php" ] && [ ! -f "Build/.php-cs-fixer.php" ]; then echo "- [ ] Configure PHP CS Fixer"; fi)
|
||||
$(if [ ! -f "phpstan.neon" ] && [ ! -f "phpstan.neon.dist" ] && [ ! -f "Build/phpstan.neon" ] && [ ! -f "Build/phpstan.neon.dist" ]; then echo "- [ ] Configure PHPStan for static analysis"; fi)
|
||||
$(if [ ! -d ".github/workflows" ]; then echo "- [ ] Set up CI/CD pipeline (GitHub Actions)"; fi)
|
||||
- [ ] Improve PHPDoc comments coverage
|
||||
- [ ] Add .editorconfig for consistent formatting
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- **TYPO3 Extension Architecture:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/
|
||||
- **Coding Guidelines:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/CodingGuidelines/
|
||||
- **Dependency Injection:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/DependencyInjection/
|
||||
- **Testing Documentation:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/Testing/
|
||||
- **Tea Extension (Best Practice):** https://github.com/TYPO3BestPractices/tea
|
||||
|
||||
---
|
||||
|
||||
*Report generated by TYPO3 Extension Conformance Checker*
|
||||
EOF
|
||||
|
||||
echo "Final report generated successfully"
|
||||
22
skills/typo3-core-contributions/.gitignore
vendored
Normal file
22
skills/typo3-core-contributions/.gitignore
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Editor files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.bak
|
||||
.*.tmp
|
||||
|
||||
# Log files
|
||||
*.log
|
||||
|
||||
# Development files
|
||||
.env
|
||||
.env.local
|
||||
21
skills/typo3-core-contributions/LICENSE
Normal file
21
skills/typo3-core-contributions/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Netresearch DTT GmbH
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
273
skills/typo3-core-contributions/README.md
Normal file
273
skills/typo3-core-contributions/README.md
Normal file
@@ -0,0 +1,273 @@
|
||||
# TYPO3 Core Contributions Skill
|
||||
|
||||
An AI skill for guiding contributions to TYPO3 Core through systematic workflows, automated quality checks, and best practices enforcement.
|
||||
|
||||
## Overview
|
||||
|
||||
This skill provides comprehensive guidance for contributing to TYPO3 Core, including:
|
||||
|
||||
- **Gerrit-based code review workflow**
|
||||
- **Automated CI/CD debugging**
|
||||
- **Commit message formatting**
|
||||
- **WIP (Work in Progress) state management**
|
||||
- **Testing and quality assurance**
|
||||
- **Account setup and prerequisites**
|
||||
|
||||
## Features
|
||||
|
||||
### 🔄 Complete Contribution Workflow
|
||||
- Step-by-step guidance from setup to patch submission
|
||||
- Automated detection of common issues
|
||||
- Best practices enforcement at every stage
|
||||
|
||||
### 🤖 CI/CD Integration
|
||||
- Systematic debugging of failed GitLab CI jobs
|
||||
- Pattern recognition for common failures
|
||||
- Automated fix suggestions
|
||||
|
||||
### ✅ Quality Gates
|
||||
- Pre-submission validation
|
||||
- Code style enforcement (CGL)
|
||||
- PHPStan static analysis
|
||||
- Comprehensive test coverage
|
||||
|
||||
### 📝 Documentation
|
||||
- Gerrit workflow reference
|
||||
- Commit message format guidelines
|
||||
- Troubleshooting guide with 60+ scenarios
|
||||
- WIP state management
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Ensure you have:
|
||||
- Git configured with your TYPO3.org email
|
||||
- SSH key uploaded to review.typo3.org
|
||||
- Docker (for DDEV) or native PHP 8.2+ environment
|
||||
|
||||
### Installation
|
||||
|
||||
Use this skill in Claude Code by referencing it from your project's skill configuration.
|
||||
|
||||
### Usage
|
||||
|
||||
The skill activates automatically when working on TYPO3 Core contributions. It guides you through:
|
||||
|
||||
1. **Setup Phase**: Prerequisites verification, environment configuration
|
||||
2. **Development Phase**: Code changes, testing, validation
|
||||
3. **Submission Phase**: Gerrit patch submission, CI monitoring, WIP management
|
||||
4. **Review Phase**: Addressing reviewer feedback, iterating on changes
|
||||
|
||||
## Scope
|
||||
|
||||
**This skill covers**: TYPO3 Core code contributions (PHP, JavaScript, CSS, tests)
|
||||
- Submission via Gerrit (review.typo3.org)
|
||||
- Git commit-msg hooks and validation
|
||||
- Forge issue tracking
|
||||
- GitLab CI/CD pipeline
|
||||
|
||||
**Not covered**: TYPO3 Documentation contributions
|
||||
- For documentation work, use: https://github.com/netresearch/typo3-docs-skill
|
||||
- Documentation uses GitHub Pull Requests, not Gerrit
|
||||
- Different format (reStructuredText) and workflows
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
typo3-core-contributions/
|
||||
├── SKILL.md # Main skill definition
|
||||
├── README.md # This file
|
||||
├── references/
|
||||
│ ├── account-setup.md # Prerequisites and account configuration
|
||||
│ ├── commit-message-format.md # Commit message standards
|
||||
│ ├── ddev-setup-workflow.md # DDEV environment setup
|
||||
│ ├── gerrit-workflow.md # Complete Gerrit submission workflow
|
||||
│ └── troubleshooting.md # 60+ troubleshooting scenarios
|
||||
├── scripts/
|
||||
│ ├── setup-typo3-coredev.sh # Automated environment setup
|
||||
│ └── verify-prerequisites.sh # Prerequisites checker
|
||||
└── assets/
|
||||
└── images/ # Workflow diagrams and screenshots
|
||||
```
|
||||
|
||||
## Key Workflows
|
||||
|
||||
### 1. Initial Setup
|
||||
|
||||
```bash
|
||||
# Verify prerequisites
|
||||
./scripts/verify-prerequisites.sh
|
||||
|
||||
# Setup TYPO3 Core development environment
|
||||
./scripts/setup-typo3-coredev.sh
|
||||
```
|
||||
|
||||
### 2. Create Patch
|
||||
|
||||
```bash
|
||||
# Create feature branch
|
||||
git checkout -b feature/issue-number-description
|
||||
|
||||
# Make changes, commit with proper format
|
||||
git commit -m "[BUGFIX] Fix indexed search null handling
|
||||
|
||||
Resolves: #105737
|
||||
Releases: main"
|
||||
```
|
||||
|
||||
### 3. Submit to Gerrit
|
||||
|
||||
```bash
|
||||
# Submit as WIP (Work in Progress)
|
||||
git push origin HEAD:refs/for/main%wip
|
||||
|
||||
# After CI passes, mark as ready
|
||||
git commit --amend --allow-empty --no-edit
|
||||
git push origin HEAD:refs/for/main%ready
|
||||
```
|
||||
|
||||
### 4. Handle CI Failures
|
||||
|
||||
The skill provides systematic debugging:
|
||||
1. Check ALL failed job logs
|
||||
2. Identify failure patterns (cgl, phpstan, unit tests)
|
||||
3. Fix all issues in ONE patchset
|
||||
4. Re-submit and verify
|
||||
|
||||
## WIP State Management
|
||||
|
||||
### Command-Line Approach (Recommended)
|
||||
|
||||
```bash
|
||||
# Set WIP state
|
||||
git push origin HEAD:refs/for/main%wip
|
||||
|
||||
# Remove WIP state
|
||||
git commit --amend --allow-empty --no-edit
|
||||
git push origin HEAD:refs/for/main%ready
|
||||
```
|
||||
|
||||
### Web UI Alternative
|
||||
|
||||
Open review URL and click "Start Review" button.
|
||||
|
||||
**Note**: SSH `gerrit review` command does NOT support WIP flags.
|
||||
|
||||
## Commit Message Format
|
||||
|
||||
Required structure:
|
||||
|
||||
```
|
||||
[TYPE] Short description (max 52 chars)
|
||||
|
||||
Extended description explaining the why and how.
|
||||
|
||||
Resolves: #12345
|
||||
Releases: main, 12.4
|
||||
```
|
||||
|
||||
**Types**: BUGFIX, FEATURE, TASK, DOCS, CLEANUP, SECURITY
|
||||
|
||||
**Required**: At least one `Resolves:` line
|
||||
|
||||
**Optional**: `Related:` (but cannot be used alone)
|
||||
|
||||
## CI/CD Debugging
|
||||
|
||||
Common failure patterns:
|
||||
|
||||
### CGL (Code Style)
|
||||
```bash
|
||||
Build/Scripts/cglFixMyCommit.sh
|
||||
git commit --amend --no-edit
|
||||
```
|
||||
|
||||
### PHPStan
|
||||
```bash
|
||||
Build/Scripts/runTests.sh -s phpstan
|
||||
# Fix reported issues
|
||||
```
|
||||
|
||||
### Unit Tests
|
||||
```bash
|
||||
Build/Scripts/runTests.sh -s unit path/to/test
|
||||
# Fix test failures
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
The skill includes comprehensive troubleshooting for:
|
||||
|
||||
- **Account Issues**: Email mismatch, SSH authentication, commit-msg hook
|
||||
- **CI Failures**: CGL, PHPStan, unit tests, functional tests
|
||||
- **Gerrit Issues**: WIP state, patch conflicts, rebase requirements
|
||||
- **Testing Issues**: Test failures, coverage gaps, fixture setup
|
||||
- **Code Quality**: Naming conventions, type safety, defensive programming
|
||||
|
||||
See `references/troubleshooting.md` for detailed solutions.
|
||||
|
||||
## Real-World Testing
|
||||
|
||||
This skill was developed and validated through:
|
||||
|
||||
- **Forge Issue #105737**: TypeError in indexed search
|
||||
- **7 patchsets** with iterative CI debugging
|
||||
- **GitHub PR #397**: Documentation improvements
|
||||
- **Live Gerrit testing**: WIP workflow validation
|
||||
|
||||
All workflows have been tested on actual TYPO3 Core submissions.
|
||||
|
||||
## Updates and Enhancements
|
||||
|
||||
Recent additions:
|
||||
|
||||
### v1.1.0 (2025-10-27)
|
||||
- ✅ WIP state management (command-line and web UI)
|
||||
- ✅ CI failure investigation protocol (423 lines)
|
||||
- ✅ Comprehensive troubleshooting guide (60+ scenarios)
|
||||
- ✅ PHPStan error guidance
|
||||
- ✅ Code style enforcement patterns
|
||||
- ✅ Documentation scope clarification
|
||||
|
||||
## Contributing
|
||||
|
||||
To improve this skill:
|
||||
|
||||
1. Test on real TYPO3 Core contributions
|
||||
2. Document edge cases in troubleshooting guide
|
||||
3. Add automation scripts for common tasks
|
||||
4. Validate against official TYPO3 documentation
|
||||
|
||||
## Resources
|
||||
|
||||
### Official TYPO3 Documentation
|
||||
- [Contribution Guide](https://docs.typo3.org/m/typo3/guide-contributionworkflow/main/en-us/)
|
||||
- [Gerrit Documentation](https://gerrit-review.googlesource.com/Documentation/user-upload.html)
|
||||
- [TYPO3 Forge](https://forge.typo3.org/)
|
||||
|
||||
### Related Skills
|
||||
- [TYPO3 Docs Skill](https://github.com/netresearch/typo3-docs-skill) - For documentation contributions
|
||||
|
||||
## License
|
||||
|
||||
MIT License - See LICENSE file for details
|
||||
|
||||
## Author
|
||||
|
||||
Created for use with Claude Code and TYPO3 Core contributions.
|
||||
|
||||
Maintained by: Netresearch DTT GmbH
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions:
|
||||
- Open an issue in this repository
|
||||
- Reference official TYPO3 documentation
|
||||
- Test workflows on live Gerrit instance
|
||||
|
||||
---
|
||||
|
||||
**Version**: 1.1.0
|
||||
**Last Updated**: 2025-10-27
|
||||
**Status**: Production-ready, validated on live submissions
|
||||
1060
skills/typo3-core-contributions/SKILL.md
Normal file
1060
skills/typo3-core-contributions/SKILL.md
Normal file
File diff suppressed because it is too large
Load Diff
78
skills/typo3-core-contributions/assets/commit-template.txt
Normal file
78
skills/typo3-core-contributions/assets/commit-template.txt
Normal file
@@ -0,0 +1,78 @@
|
||||
[BUGFIX|TASK|FEATURE|DOCS|SECURITY] Subject line (max 52 chars, imperative)
|
||||
|
||||
# Detailed description explaining how and why (not what):
|
||||
# - Lines wrapped at 72 characters
|
||||
# - Explain context and reasoning
|
||||
# - Don't repeat Forge issue content
|
||||
# - Don't describe reproduction steps
|
||||
#
|
||||
# Use bullet points for lists:
|
||||
# * First item
|
||||
# * Second item
|
||||
# * Third item
|
||||
|
||||
Resolves: #
|
||||
Releases: main, 13.4, 12.4
|
||||
# Change-Id will be added automatically by git hook - DO NOT edit manually!
|
||||
|
||||
# ============================================================================
|
||||
# TYPO3 Commit Message Format Guide
|
||||
# ============================================================================
|
||||
#
|
||||
# SUBJECT LINE
|
||||
# ------------
|
||||
# Format: [TYPE] Description
|
||||
#
|
||||
# Types:
|
||||
# [BUGFIX] - Bug fixes
|
||||
# [FEATURE] - New features (main branch only)
|
||||
# [TASK] - Refactoring, cleanup, miscellaneous
|
||||
# [DOCS] - Documentation changes
|
||||
# [SECURITY] - Security vulnerability fixes
|
||||
#
|
||||
# Breaking changes: [!!!][TYPE] Description
|
||||
#
|
||||
# Rules:
|
||||
# - Imperative mood (Fix, Add, Remove - not Fixed, Added, Removed)
|
||||
# - Start with uppercase letter
|
||||
# - Max 52 characters (recommended), 72 absolute limit
|
||||
# - No period at end
|
||||
# - Describe what now works, not what was broken
|
||||
#
|
||||
# DESCRIPTION BODY
|
||||
# ----------------
|
||||
# - Explain HOW and WHY, not WHAT (code shows what)
|
||||
# - Leave blank line after subject
|
||||
# - Wrap at 72 characters (URLs can be longer)
|
||||
# - Use asterisks (*) for bullet points
|
||||
#
|
||||
# FOOTER TAGS
|
||||
# -----------
|
||||
# Resolves: #12345 - Closes issue on merge (features/tasks)
|
||||
# Related: #12345 - Links issue without closing (bugfixes)
|
||||
# Releases: main, 13.4 - Target versions (comma-separated)
|
||||
# Change-Id: I... - Auto-generated, DO NOT modify!
|
||||
#
|
||||
# EXAMPLES
|
||||
# --------
|
||||
# Good:
|
||||
# [BUGFIX] Fix null pointer in indexed search
|
||||
# [FEATURE] Add WebP image format support
|
||||
# [TASK] Refactor cache manager for better performance
|
||||
# [!!!][TASK] Drop support for PHP 7.4
|
||||
#
|
||||
# Bad:
|
||||
# [BUGFIX] fixed bug (past tense)
|
||||
# [FEATURE] Adds webp support (wrong tense)
|
||||
# [TASK] refactoring cache (lowercase)
|
||||
# [BUGFIX] Fix null pointer exception. (period at end)
|
||||
#
|
||||
# VALIDATION
|
||||
# ----------
|
||||
# Run: ./scripts/validate-commit-message.py
|
||||
#
|
||||
# MORE INFO
|
||||
# ---------
|
||||
# See: references/commit-message-format.md
|
||||
# Docs: https://docs.typo3.org/m/typo3/guide-contributionworkflow/
|
||||
# ============================================================================
|
||||
@@ -0,0 +1,52 @@
|
||||
# SKILL.md Refactoring Summary
|
||||
|
||||
**Date:** 2025-11-14
|
||||
**Version Change:** 1.3.0 → 1.4.0
|
||||
**Skill:** typo3-core-contributions
|
||||
|
||||
## Changes Applied
|
||||
|
||||
### Pattern 1: Removed "## Overview" Section
|
||||
- **Before:** Lines 21-23 contained brief "## Overview" section
|
||||
- **After:** Removed entire section
|
||||
- **Rationale:** Content duplicated by YAML description and "When to Use This Skill" section
|
||||
|
||||
### Pattern 2: Converted "## Best Practices" to Imperative Form
|
||||
- **Before:** "## Best Practices" with numbered list and subsection
|
||||
- **After:** "## Contribution Workflow Standards" with imperative "When X" format
|
||||
- **Changes:**
|
||||
|
||||
#### When managing commits (5 standards)
|
||||
- Converted from general practices to specific commit management procedures
|
||||
- Emphasized proactive skill usage (typo3-conformance-skill, typo3-testing-skill)
|
||||
|
||||
#### When maintaining patches (5 standards)
|
||||
- Extracted patch maintenance guidance into dedicated section
|
||||
- Action-oriented instructions for rebase, review, feedback
|
||||
|
||||
#### When writing code (5 standards)
|
||||
- Focused on code quality and framework patterns
|
||||
- Clear integration points with complementary skills
|
||||
|
||||
#### When handling CI failures (4 standards)
|
||||
- Separated CI troubleshooting into distinct workflow
|
||||
- Emphasized local validation and root cause analysis
|
||||
|
||||
## Impact Analysis
|
||||
|
||||
**Readability:** Improved - organized by workflow context
|
||||
**Consistency:** Aligned with skill-creator best practices
|
||||
**Usability:** Enhanced - clear triggers for when to apply each standard
|
||||
**Workflow Integration:** Better integration with complementary skills
|
||||
|
||||
## Files Modified
|
||||
|
||||
- `/SKILL.md` (lines 1-1057)
|
||||
|
||||
## Verification
|
||||
|
||||
- Version number updated in YAML frontmatter: ✓
|
||||
- Overview section removed: ✓
|
||||
- Best Practices converted to Contribution Workflow Standards: ✓
|
||||
- All 19 standards preserved with improved organization: ✓
|
||||
- Skill integration guidance maintained: ✓
|
||||
275
skills/typo3-core-contributions/references/account-setup.md
Normal file
275
skills/typo3-core-contributions/references/account-setup.md
Normal file
@@ -0,0 +1,275 @@
|
||||
# TYPO3 Core Contribution Account Setup Guide
|
||||
|
||||
Complete guide for setting up all required accounts for TYPO3 Core contributions.
|
||||
|
||||
## Overview
|
||||
|
||||
Three accounts are required for TYPO3 Core contribution:
|
||||
1. **TYPO3.org Account** - Central authentication for all TYPO3 services
|
||||
2. **Gerrit Account with SSH** - Code review and patch submission
|
||||
3. **Slack Access** - Community communication and support
|
||||
|
||||
## 1. TYPO3.org Account
|
||||
|
||||
### Registration
|
||||
|
||||
1. Visit the signup page: https://my.typo3.org/index.php?id=2
|
||||
|
||||
2. Fill in the registration form:
|
||||
- **Username**: Choose alphanumeric identifier (avoid special characters like `@` or `!`)
|
||||
- **Email Address**: Use email for Forge and Gerrit notifications
|
||||
- **Full Name**: Use real name (community values genuine identification)
|
||||
- **Password**: Create strong password (use password manager recommended)
|
||||
|
||||
3. Submit the form
|
||||
|
||||
4. Check your email for verification message
|
||||
|
||||
5. Click verification link to activate account
|
||||
|
||||
### What This Account Provides
|
||||
|
||||
- Access to Forge issue tracker
|
||||
- Authentication for Gerrit code review
|
||||
- Access to my.typo3.org profile management
|
||||
- Community member identification
|
||||
|
||||
### Important Notes
|
||||
|
||||
- Username cannot be changed after registration
|
||||
- Consider using personal email (not corporate) if contributing independently
|
||||
- This account will be visible in git commits and Gerrit reviews
|
||||
|
||||
## 2. Gerrit Account Setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Active TYPO3.org account
|
||||
- SSH key pair (will create if needed)
|
||||
|
||||
### Step 1: Sign In to Gerrit
|
||||
|
||||
1. Visit https://review.typo3.org
|
||||
2. Click **Sign In** button (top right)
|
||||
3. Authenticate with your TYPO3.org credentials
|
||||
4. You'll be redirected back to Gerrit
|
||||
|
||||
### Step 2: Generate SSH Key Pair
|
||||
|
||||
SSH keys are required for pushing patches to Gerrit.
|
||||
|
||||
#### Linux / macOS
|
||||
|
||||
```bash
|
||||
# Generate SSH key pair
|
||||
ssh-keygen -t ed25519 -C "your-email@example.org"
|
||||
|
||||
# Default location: ~/.ssh/id_ed25519
|
||||
# Press Enter to accept default location
|
||||
# Optionally set a passphrase for additional security
|
||||
|
||||
# View your public key
|
||||
cat ~/.ssh/id_ed25519.pub
|
||||
```
|
||||
|
||||
#### Windows
|
||||
|
||||
**Option A: Git Bash (Recommended)**
|
||||
```bash
|
||||
# Same commands as Linux/macOS above
|
||||
ssh-keygen -t ed25519 -C "your-email@example.org"
|
||||
```
|
||||
|
||||
**Option B: PuTTYgen**
|
||||
1. Download and install PuTTY: https://www.putty.org/
|
||||
2. Run PuTTYgen
|
||||
3. Click "Generate" and move mouse randomly
|
||||
4. Save private key (*.ppk file)
|
||||
5. Copy public key from text area
|
||||
|
||||
### Step 3: Add Public Key to Gerrit
|
||||
|
||||
1. Click your profile icon (top right in Gerrit)
|
||||
2. Select **Settings** from dropdown
|
||||
3. Click **SSH Keys** in left sidebar
|
||||
4. Paste your **public key** content (entire content of `id_ed25519.pub` or `id_rsa.pub`)
|
||||
5. Click **Add New SSH Key**
|
||||
|
||||
**Important**: Only add the PUBLIC key, never the private key!
|
||||
|
||||
### Step 4: Test SSH Connection
|
||||
|
||||
```bash
|
||||
# Test connection to Gerrit
|
||||
ssh -p 29418 <YOUR_USERNAME>@review.typo3.org
|
||||
|
||||
# Expected output:
|
||||
# **** Welcome to Gerrit Code Review ****
|
||||
# Hi <Your Name>, you have successfully connected over SSH.
|
||||
```
|
||||
|
||||
If you see the welcome message, SSH is configured correctly!
|
||||
|
||||
### Multiple Devices
|
||||
|
||||
If you work on multiple computers:
|
||||
|
||||
**Option 1: Copy Private Key**
|
||||
- Copy `~/.ssh/id_ed25519` (private key) to other machines
|
||||
- Set proper permissions: `chmod 600 ~/.ssh/id_ed25519`
|
||||
- Not recommended for security reasons
|
||||
|
||||
**Option 2: Generate Separate Keys (Recommended)**
|
||||
- Generate new key pair on each device
|
||||
- Add all public keys to Gerrit settings
|
||||
- Gerrit supports multiple SSH keys per account
|
||||
- More secure: compromised device doesn't affect others
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
**"Permission denied (publickey)"**
|
||||
- Verify key is added to Gerrit: Settings → SSH Keys
|
||||
- Check key permissions: `chmod 600 ~/.ssh/id_ed25519`
|
||||
- Test with verbose: `ssh -vvv -p 29418 <username>@review.typo3.org`
|
||||
|
||||
**"Connection refused"**
|
||||
- Check firewall settings
|
||||
- Verify port 29418 is accessible
|
||||
- Try from different network
|
||||
|
||||
**"Host key verification failed"**
|
||||
- Accept host key: `ssh-keyscan -p 29418 review.typo3.org >> ~/.ssh/known_hosts`
|
||||
|
||||
## 3. TYPO3 Slack Workspace
|
||||
|
||||
### Joining Slack
|
||||
|
||||
1. Visit https://typo3.slack.com
|
||||
|
||||
2. Click **Create an account** or **Sign in**
|
||||
|
||||
3. Use same email as TYPO3.org account (recommended for consistency)
|
||||
|
||||
4. Complete Slack registration
|
||||
|
||||
5. You'll receive invitation to TYPO3 workspace
|
||||
|
||||
### Required Channels
|
||||
|
||||
**#typo3-cms-coredev** (Essential)
|
||||
- Core development discussions
|
||||
- Patch review requests
|
||||
- Technical questions
|
||||
- Get help from core team
|
||||
|
||||
### Recommended Channels
|
||||
|
||||
**#typo3-cms**
|
||||
- General TYPO3 CMS discussions
|
||||
- User questions
|
||||
- Extension development
|
||||
|
||||
**#random**
|
||||
- Off-topic chat
|
||||
- Community social
|
||||
|
||||
**#announce**
|
||||
- Official announcements
|
||||
- Release notifications
|
||||
|
||||
### Using Slack Effectively
|
||||
|
||||
**Asking for Reviews**:
|
||||
```
|
||||
I've submitted a patch for issue #105737 (indexed search crash).
|
||||
Would appreciate reviews: https://review.typo3.org/c/Packages/TYPO3.CMS/+/12345
|
||||
```
|
||||
|
||||
**Asking Questions**:
|
||||
```
|
||||
Working on #105737, need clarification on preg_replace error handling.
|
||||
Should I use fallback or throw exception? Context: [brief explanation]
|
||||
```
|
||||
|
||||
**Best Practices**:
|
||||
- Search before asking (knowledge base exists)
|
||||
- Provide context and Forge/Gerrit links
|
||||
- Be patient (volunteers respond when available)
|
||||
- Use threads for discussions
|
||||
- Thank people who help!
|
||||
|
||||
### Slack Etiquette
|
||||
|
||||
- **Don't** @here or @channel unless critical
|
||||
- **Do** use threads to keep discussions organized
|
||||
- **Don't** DM core team members without asking first
|
||||
- **Do** share knowledge when you can help others
|
||||
- **Don't** expect immediate responses (volunteers have lives!)
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
Before proceeding with development, verify:
|
||||
|
||||
- [ ] TYPO3.org account created and email verified
|
||||
- [ ] Can sign in to https://forge.typo3.org
|
||||
- [ ] Can sign in to https://review.typo3.org
|
||||
- [ ] SSH key added to Gerrit
|
||||
- [ ] SSH connection to Gerrit successful: `ssh -p 29418 <user>@review.typo3.org`
|
||||
- [ ] Joined TYPO3 Slack workspace
|
||||
- [ ] Member of #typo3-cms-coredev channel
|
||||
|
||||
Run `scripts/verify-prerequisites.sh` to automatically check most of these!
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### SSH Key Security
|
||||
|
||||
- **Never share private keys** - TYPO3 team will never ask for them
|
||||
- **Use strong passphrase** - Protects key if device is compromised
|
||||
- **Rotate keys periodically** - Generate new keys annually
|
||||
- **Delete old keys** - Remove unused keys from Gerrit settings
|
||||
|
||||
### Account Security
|
||||
|
||||
- **Use unique strong password** - Use password manager
|
||||
- **Enable 2FA if available** - Additional security layer
|
||||
- **Log out on shared devices** - Don't stay signed in
|
||||
- **Review SSH keys regularly** - Remove keys from old devices
|
||||
|
||||
### Privacy Considerations
|
||||
|
||||
- Your name and email will be visible in:
|
||||
- Git commit history
|
||||
- Gerrit reviews
|
||||
- Forge issue comments
|
||||
- Consider using professional email if contributing as individual
|
||||
- Company contributions may require corporate email
|
||||
|
||||
## Next Steps
|
||||
|
||||
After completing account setup:
|
||||
|
||||
1. Proceed to **Environment Setup** (Phase 2 in main workflow)
|
||||
2. Configure Git for TYPO3 contributions
|
||||
3. Clone TYPO3 repository
|
||||
4. Install Git hooks
|
||||
5. Start contributing!
|
||||
|
||||
## Support Resources
|
||||
|
||||
- **Forge Account Issues**: https://forge.typo3.org/projects/typo3cms-core
|
||||
- **Gerrit SSH Help**: https://review.typo3.org/Documentation/user-upload.html
|
||||
- **Slack Support**: Ask in #typo3-cms-coredev
|
||||
- **Documentation**: https://docs.typo3.org/m/typo3/guide-contributionworkflow/
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Service | URL | Purpose |
|
||||
|---------|-----|---------|
|
||||
| TYPO3.org Registration | https://my.typo3.org/index.php?id=2 | Create account |
|
||||
| TYPO3.org Profile | https://my.typo3.org | Manage profile |
|
||||
| Forge | https://forge.typo3.org | Issue tracking |
|
||||
| Gerrit | https://review.typo3.org | Code review |
|
||||
| Gerrit SSH Test | `ssh -p 29418 <user>@review.typo3.org` | Verify connection |
|
||||
| Slack | https://typo3.slack.com | Community chat |
|
||||
| Documentation | https://docs.typo3.org/m/typo3/guide-contributionworkflow/ | Full guide |
|
||||
@@ -0,0 +1,477 @@
|
||||
# TYPO3 Commit Message Format Specification
|
||||
|
||||
Complete specification for TYPO3 Core contribution commit messages with examples and validation rules.
|
||||
|
||||
## Structure Overview
|
||||
|
||||
```
|
||||
[TYPE] Subject line (max 52 chars recommended, 72 absolute limit)
|
||||
|
||||
Detailed description explaining how and why the changes were made.
|
||||
Lines wrapped at 72 characters. Explain the context and reasoning
|
||||
behind the implementation approach.
|
||||
|
||||
Multiple paragraphs are allowed. Use bullet points with asterisks (*)
|
||||
for lists:
|
||||
|
||||
* First item with detailed explanation
|
||||
* Second item
|
||||
* Third item
|
||||
|
||||
Resolves: #12345
|
||||
Related: #12346
|
||||
Releases: main, 13.4, 12.4
|
||||
Change-Id: I1234567890abcdef1234567890abcdef12345678
|
||||
```
|
||||
|
||||
## Subject Line
|
||||
|
||||
### Format
|
||||
|
||||
`[TYPE] Description starting with uppercase verb in imperative mood`
|
||||
|
||||
### Commit Types
|
||||
|
||||
| Type | Purpose | Branch Restrictions |
|
||||
|------|---------|-------------------|
|
||||
| `[BUGFIX]` | Bug fixes | All branches |
|
||||
| `[FEATURE]` | New functionality | main branch only |
|
||||
| `[TASK]` | Refactoring, cleanup, misc | All branches |
|
||||
| `[DOCS]` | Documentation changes | All branches |
|
||||
| `[SECURITY]` | Security vulnerability fixes | All branches |
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
Use `[!!!]` prefix before type for breaking changes:
|
||||
|
||||
```
|
||||
[!!!][FEATURE] Remove deprecated TypoScript syntax
|
||||
|
||||
[!!!][TASK] Drop support for PHP 7.4
|
||||
```
|
||||
|
||||
**Important**: Deprecations must NOT use `[!!!]` prefix!
|
||||
|
||||
### Length Limits
|
||||
|
||||
- **Recommended**: 52 characters
|
||||
- **Absolute maximum**: 72 characters
|
||||
- Breaking changes get 5 extra chars: `[!!!]` is not counted against limit
|
||||
|
||||
### Imperative Mood
|
||||
|
||||
Subject must use imperative present tense (command form):
|
||||
|
||||
✅ **Correct**:
|
||||
- `Fix memory leak in cache manager`
|
||||
- `Add support for WebP images`
|
||||
- `Remove deprecated method calls`
|
||||
- `Update documentation for hooks`
|
||||
|
||||
❌ **Wrong**:
|
||||
- `Fixed memory leak` (past tense)
|
||||
- `Fixing memory leak` (present continuous)
|
||||
- `Fixes memory leak` (present tense)
|
||||
- `Memory leak fix` (noun phrase)
|
||||
|
||||
**Test**: "If applied, this commit will _[your subject]_"
|
||||
- "If applied, this commit will **fix memory leak**" ✅
|
||||
- "If applied, this commit will **fixed memory leak**" ❌
|
||||
|
||||
### Capitalization
|
||||
|
||||
- Start description with uppercase letter after `[TYPE]`
|
||||
- No period at the end
|
||||
|
||||
✅ `[BUGFIX] Fix null pointer exception in indexer`
|
||||
❌ `[BUGFIX] fix null pointer exception in indexer`
|
||||
❌ `[BUGFIX] Fix null pointer exception in indexer.`
|
||||
|
||||
### What to Describe
|
||||
|
||||
Describe **what now works**, not what was broken:
|
||||
|
||||
✅ `Allow cancelling file exists modal`
|
||||
❌ `Cancelling the file exists modal works now`
|
||||
|
||||
✅ `Limit element browser to default language pages`
|
||||
❌ `Element Browser should only render default language pages`
|
||||
|
||||
## Description Body
|
||||
|
||||
### Purpose
|
||||
|
||||
Explain the **how** and **why** of changes, not the **what** (code shows what).
|
||||
|
||||
### Guidelines
|
||||
|
||||
- Wrap lines at 72 characters (URLs can be longer)
|
||||
- Leave blank line after subject
|
||||
- Explain context and reasoning
|
||||
- **Don't** repeat Forge issue content
|
||||
- **Don't** describe reproduction steps (belong in Forge)
|
||||
- **Do** explain non-obvious implementation choices
|
||||
- **Do** mention side effects or impacts
|
||||
|
||||
### Bullet Points
|
||||
|
||||
Use asterisks (`*`) with hanging indents:
|
||||
|
||||
```
|
||||
This change improves performance by:
|
||||
|
||||
* Caching compiled templates in memory
|
||||
* Reducing database queries from N+1 to 1
|
||||
* Pre-loading frequently accessed resources
|
||||
```
|
||||
|
||||
### Long URLs
|
||||
|
||||
Lines exceeding 72 chars are acceptable for URLs. Use numbered references:
|
||||
|
||||
```
|
||||
This implements the W3C recommendation [1] for accessible forms.
|
||||
|
||||
Additional context can be found in the TYPO3 RFC [2].
|
||||
|
||||
[1] https://www.w3.org/WAI/WCAG21/Understanding/labels-or-instructions.html
|
||||
[2] https://wiki.typo3.org/Category:T3DD12/Sessions/Accessibility
|
||||
```
|
||||
|
||||
## Footer Tags
|
||||
|
||||
### Required Format
|
||||
|
||||
`Tag: value` (colon followed by space)
|
||||
|
||||
### Resolves **(REQUIRED)**
|
||||
|
||||
Closes Forge issue when patch is merged:
|
||||
|
||||
```
|
||||
Resolves: #12345
|
||||
```
|
||||
|
||||
Multiple issues (one per line):
|
||||
```
|
||||
Resolves: #12345
|
||||
Resolves: #12346
|
||||
```
|
||||
|
||||
**Critical Rule**: Every commit MUST have at least one `Resolves:` line. The commit-msg hook will reject commits without it.
|
||||
|
||||
**When to use**:
|
||||
- Features: MUST use Resolves
|
||||
- Tasks: MUST use Resolves
|
||||
- Bugfixes: MUST use Resolves
|
||||
- All commit types: ALWAYS use Resolves
|
||||
|
||||
**Note**: For features and tasks, `Resolves:` closes the issue on merge. For bugfixes, you can use `Related:` in addition to `Resolves:` if needed, but `Resolves:` is still mandatory.
|
||||
|
||||
### Related **(OPTIONAL)**
|
||||
|
||||
Links issue without closing it:
|
||||
|
||||
```
|
||||
Related: #12345
|
||||
```
|
||||
|
||||
**Critical Rule**: `Related:` CANNOT be used alone - you MUST have at least one `Resolves:` line in addition to any `Related:` lines. The commit-msg hook will reject commits with only `Related:` tags.
|
||||
|
||||
**When to use** (in addition to Resolves):
|
||||
- Bugfixes: Use Related for issues that should stay open
|
||||
- Partial fixes: Related for multi-step fixes where issue remains open
|
||||
- Context: Link related discussions or issues
|
||||
- Cross-references: Link to related work or documentation
|
||||
|
||||
### Releases
|
||||
|
||||
Target versions (comma-separated):
|
||||
|
||||
```
|
||||
Releases: main, 13.4, 12.4
|
||||
```
|
||||
|
||||
**Format**:
|
||||
- `main` - Current development branch
|
||||
- `13.4` - Patch release version
|
||||
- `12.4` - LTS version
|
||||
|
||||
**Rules**:
|
||||
- Always include target versions
|
||||
- List from newest to oldest
|
||||
- Features go to `main` only
|
||||
- Bugfixes can target multiple releases
|
||||
|
||||
### Change-Id
|
||||
|
||||
Auto-generated by git commit-msg hook:
|
||||
|
||||
```
|
||||
Change-Id: I1234567890abcdef1234567890abcdef12345678
|
||||
```
|
||||
|
||||
**Critical Rules**:
|
||||
- **NEVER** manually add Change-Id
|
||||
- **NEVER** modify existing Change-Id
|
||||
- **NEVER** remove Change-Id
|
||||
- Git hook generates this automatically
|
||||
- Required for Gerrit to track patch updates
|
||||
|
||||
### Depends (Documentation Only)
|
||||
|
||||
For documentation patches referencing core changes:
|
||||
|
||||
```
|
||||
Depends: I1234567890abcdef1234567890abcdef12345678
|
||||
```
|
||||
|
||||
Only used in typo3/cms-docs repository.
|
||||
|
||||
### Reverts
|
||||
|
||||
For reverting patches:
|
||||
|
||||
```
|
||||
[TASK] Revert "[FEATURE] Introduce YAML imports"
|
||||
|
||||
This reverts commit abc123def456.
|
||||
|
||||
Resolves: #12347
|
||||
Reverts: I1234567890abcdef1234567890abcdef12345678
|
||||
```
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Example 1: Bugfix
|
||||
|
||||
```
|
||||
[BUGFIX] Prevent null pointer in indexed search
|
||||
|
||||
The preg_replace function returns null on PCRE errors like
|
||||
PREG_BAD_UTF8_ERROR. Passing null to mb_strcut triggers TypeError
|
||||
in PHP 8.2+.
|
||||
|
||||
Add null check with fallback to original content, ensuring type
|
||||
safety while maintaining graceful degradation for malformed input.
|
||||
|
||||
Resolves: #105737
|
||||
Releases: main, 13.4, 12.4
|
||||
Change-Id: I1234567890abcdef1234567890abcdef12345678
|
||||
```
|
||||
|
||||
### Example 2: Feature
|
||||
|
||||
```
|
||||
[FEATURE] Add WebP image format support
|
||||
|
||||
Implement WebP processing in image manipulation stack:
|
||||
|
||||
* Add WebP MIME type detection
|
||||
* Integrate libwebp encoding/decoding
|
||||
* Update image quality settings for WebP
|
||||
* Add configuration options for compression
|
||||
|
||||
WebP provides 25-30% better compression than JPEG while maintaining
|
||||
quality, significantly improving page load times.
|
||||
|
||||
Resolves: #98765
|
||||
Releases: main
|
||||
Change-Id: Iabcdef1234567890abcdef1234567890abcdef12
|
||||
```
|
||||
|
||||
### Example 3: Task with Breaking Change
|
||||
|
||||
```
|
||||
[!!!][TASK] Drop PHP 7.4 support
|
||||
|
||||
PHP 7.4 reached end-of-life in November 2022 and no longer receives
|
||||
security updates. Remove compatibility code and leverage PHP 8.0+
|
||||
features:
|
||||
|
||||
* Remove PHP 7.4 compatibility polyfills
|
||||
* Update composer.json to require PHP >= 8.0
|
||||
* Use union types and match expressions
|
||||
* Enable strict type declarations globally
|
||||
|
||||
Resolves: #99888
|
||||
Releases: main
|
||||
Change-Id: I9876543210fedcba9876543210fedcba98765432
|
||||
```
|
||||
|
||||
### Example 4: Documentation
|
||||
|
||||
```
|
||||
[DOCS] Update contribution workflow guide
|
||||
|
||||
Clarify git setup instructions and add troubleshooting section
|
||||
for common SSH key issues reported in #typo3-cms-coredev.
|
||||
|
||||
Related: #12345
|
||||
Releases: main
|
||||
Change-Id: Iaa11bb22cc33dd44ee55ff66gg77hh88ii99jj00
|
||||
```
|
||||
|
||||
## Validation Rules
|
||||
|
||||
### Subject Line
|
||||
|
||||
- [ ] Starts with valid commit type: `[BUGFIX]`, `[FEATURE]`, `[TASK]`, `[DOCS]`, or `[SECURITY]`
|
||||
- [ ] Breaking changes use `[!!!]` prefix correctly
|
||||
- [ ] Description starts with uppercase letter
|
||||
- [ ] Uses imperative mood
|
||||
- [ ] No period at end
|
||||
- [ ] Length ≤ 52 chars (recommended) or ≤ 72 chars (absolute)
|
||||
- [ ] No extension names (EXT:) in subject
|
||||
|
||||
### Body
|
||||
|
||||
- [ ] Blank line after subject (if body exists)
|
||||
- [ ] Lines wrapped at 72 chars (except URLs)
|
||||
- [ ] Explains how and why, not what
|
||||
- [ ] No reproduction steps (belong in Forge)
|
||||
|
||||
### Footer
|
||||
|
||||
- [ ] `Resolves:` present **(REQUIRED for ALL commits)**
|
||||
**Critical**: The commit-msg hook WILL REJECT commits without at least one `Resolves:` line
|
||||
- [ ] `Related:` used only in addition to `Resolves:` (optional, cannot be used alone)
|
||||
- [ ] `Releases:` present with valid versions
|
||||
- [ ] `Change-Id:` present (added by hook)
|
||||
- [ ] Proper format: `Tag: value` (colon + space)
|
||||
- [ ] Issue references use `#` prefix: `#12345`
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
### ❌ Wrong: Vague Subject
|
||||
|
||||
```
|
||||
[TASK] Improve extension configuration
|
||||
```
|
||||
|
||||
### ✅ Correct: Specific Subject
|
||||
|
||||
```
|
||||
[TASK] Add validation for extension configuration arrays
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ❌ Wrong: Past Tense
|
||||
|
||||
```
|
||||
[BUGFIX] Fixed cache invalidation in frontend
|
||||
```
|
||||
|
||||
### ✅ Correct: Imperative Mood
|
||||
|
||||
```
|
||||
[BUGFIX] Fix cache invalidation in frontend
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ❌ Wrong: No Footer Tags
|
||||
|
||||
```
|
||||
[FEATURE] Add dark mode support
|
||||
|
||||
Implements dark mode toggle with user preference storage.
|
||||
```
|
||||
|
||||
### ✅ Correct: Complete Footer
|
||||
|
||||
```
|
||||
[FEATURE] Add dark mode support
|
||||
|
||||
Implements dark mode toggle with user preference storage.
|
||||
|
||||
Resolves: #12345
|
||||
Releases: main
|
||||
Change-Id: I1234567890abcdef1234567890abcdef12345678
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ❌ Wrong: Comma-Separated Issues
|
||||
|
||||
```
|
||||
Resolves: #12345, #12346, #12347
|
||||
```
|
||||
|
||||
### ✅ Correct: One Per Line
|
||||
|
||||
```
|
||||
Resolves: #12345
|
||||
Resolves: #12346
|
||||
Resolves: #12347
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ❌ Wrong: Missing Space After Colon
|
||||
|
||||
```
|
||||
Releases:main, 13.4
|
||||
```
|
||||
|
||||
### ✅ Correct: Space After Colon
|
||||
|
||||
```
|
||||
Releases: main, 13.4
|
||||
```
|
||||
|
||||
## Tools
|
||||
|
||||
### Validation
|
||||
|
||||
Use `scripts/validate-commit-message.py`:
|
||||
|
||||
```bash
|
||||
# Validate last commit
|
||||
./scripts/validate-commit-message.py
|
||||
|
||||
# Validate specific file
|
||||
./scripts/validate-commit-message.py --file commit-msg.txt
|
||||
|
||||
# Strict mode (warnings as errors)
|
||||
./scripts/validate-commit-message.py --strict
|
||||
```
|
||||
|
||||
### Generation
|
||||
|
||||
Use `scripts/create-commit-message.py`:
|
||||
|
||||
```bash
|
||||
# Interactive generator
|
||||
./scripts/create-commit-message.py --issue 105737 --type BUGFIX
|
||||
|
||||
# With breaking change
|
||||
./scripts/create-commit-message.py --issue 98765 --type FEATURE --breaking
|
||||
```
|
||||
|
||||
### Template
|
||||
|
||||
Copy `assets/commit-template.txt` to `~/.gitmessage.txt`:
|
||||
|
||||
```bash
|
||||
git config --global commit.template ~/.gitmessage.txt
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- **Official Guide**: https://docs.typo3.org/m/typo3/guide-contributionworkflow/main/en-us/Appendix/CommitMessage.html
|
||||
- **Gerrit Documentation**: https://review.typo3.org/Documentation/
|
||||
- **TYPO3 Forge**: https://forge.typo3.org
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Element | Format | Example |
|
||||
|---------|--------|---------|
|
||||
| Bugfix | `[BUGFIX] Description` | `[BUGFIX] Fix null pointer in indexer` |
|
||||
| Feature | `[FEATURE] Description` | `[FEATURE] Add WebP support` |
|
||||
| Task | `[TASK] Description` | `[TASK] Refactor cache manager` |
|
||||
| Breaking | `[!!!][TYPE] Description` | `[!!!][TASK] Drop PHP 7.4 support` |
|
||||
| Resolves | `Resolves: #12345` | Closes issue on merge |
|
||||
| Related | `Related: #12345` | Links without closing |
|
||||
| Releases | `Releases: main, 13.4` | Target versions |
|
||||
460
skills/typo3-core-contributions/references/commit-msg-hook.md
Normal file
460
skills/typo3-core-contributions/references/commit-msg-hook.md
Normal file
@@ -0,0 +1,460 @@
|
||||
# TYPO3 Commit Message Hook
|
||||
|
||||
Deep dive into the `Build/git-hooks/commit-msg` hook: validation rules, error messages, and troubleshooting.
|
||||
|
||||
## Overview
|
||||
|
||||
The commit-msg hook is a Git client-side hook that validates commit messages before they're created. TYPO3 uses this to enforce commit message standards and automatically add Change-Id for Gerrit tracking.
|
||||
|
||||
**Location**: `Build/git-hooks/commit-msg`
|
||||
**Installed to**: `.git/hooks/commit-msg`
|
||||
|
||||
## Installation
|
||||
|
||||
### Automated (Recommended)
|
||||
|
||||
```bash
|
||||
composer gerrit:setup
|
||||
```
|
||||
|
||||
This command:
|
||||
1. Copies hook from `Build/git-hooks/commit-msg` to `.git/hooks/commit-msg`
|
||||
2. Makes it executable
|
||||
3. Sets up Gerrit configuration
|
||||
|
||||
### Manual
|
||||
|
||||
```bash
|
||||
# Copy hook
|
||||
cp Build/git-hooks/commit-msg .git/hooks/commit-msg
|
||||
|
||||
# Make executable
|
||||
chmod +x .git/hooks/commit-msg
|
||||
```
|
||||
|
||||
### Verify Installation
|
||||
|
||||
```bash
|
||||
# Check if hook exists and is executable
|
||||
ls -la .git/hooks/commit-msg
|
||||
|
||||
# Expected output:
|
||||
# -rwxr-xr-x 1 user group 8192 Dec 15 10:00 .git/hooks/commit-msg
|
||||
```
|
||||
|
||||
## Hook Functions
|
||||
|
||||
### 1. Change-Id Generation
|
||||
|
||||
**Purpose**: Auto-generate unique Change-Id for Gerrit patch tracking
|
||||
|
||||
**Function**: `add_ChangeId()`
|
||||
|
||||
**Behavior**:
|
||||
- Generates unique hash based on commit content
|
||||
- Adds `Change-Id: I<hash>` to commit message footer
|
||||
- Skips if Change-Id already exists
|
||||
- Skips for fixup!/squash! commits
|
||||
- Places Change-Id after Resolves/Releases footer
|
||||
|
||||
**Format**:
|
||||
```
|
||||
Change-Id: I1234567890abcdef1234567890abcdef12345678
|
||||
```
|
||||
|
||||
**Critical Rules**:
|
||||
- NEVER manually add Change-Id
|
||||
- NEVER modify existing Change-Id
|
||||
- NEVER remove Change-Id
|
||||
- Same Change-Id = same patch (for updates)
|
||||
- Different Change-Id = new patch
|
||||
|
||||
### 2. Line Length Validation
|
||||
|
||||
**Function**: `checkForLineLength()`
|
||||
|
||||
**Rules**:
|
||||
- Maximum line length: 72 characters
|
||||
- Applies to subject and body
|
||||
- Excludes comment lines (starting with `#`)
|
||||
- URLs can exceed limit
|
||||
|
||||
**Error Message**:
|
||||
```
|
||||
The maximum line length of 72 characters is exceeded.
|
||||
```
|
||||
|
||||
**Location**: Line 200 in hook
|
||||
|
||||
### 3. Commit Type Validation
|
||||
|
||||
**Function**: `checkForCommitType()`
|
||||
|
||||
**Rules**:
|
||||
- First line must contain commit type in brackets
|
||||
- Valid types: `[BUGFIX]`, `[FEATURE]`, `[TASK]`, `[DOCS]`, `[SECURITY]`
|
||||
- Breaking changes: `[!!!][TYPE]`
|
||||
|
||||
**Regex**: `/^\[^]]+\] .+$/`
|
||||
|
||||
**Error Message**:
|
||||
```
|
||||
Your first line has to contain a commit type like '[BUGFIX]'.
|
||||
```
|
||||
|
||||
**Location**: Line 209 in hook
|
||||
|
||||
### 4. Resolves Tag Validation
|
||||
|
||||
**Function**: `checkForResolves()`
|
||||
|
||||
**Rules**:
|
||||
- Every commit MUST have at least one `Resolves:` or `Fixes:` line
|
||||
- Format: `Resolves: #<number>` or `Fixes: #<number>`
|
||||
- Must be on separate line (not inline)
|
||||
- Issue number must be numeric
|
||||
|
||||
**Regex**: `/^(Resolves|Fixes): #[0-9]+$/`
|
||||
|
||||
**Error Message** (as of v1.1):
|
||||
```
|
||||
You need at least one 'Resolves|Fixes: #<issue number>' line.
|
||||
```
|
||||
|
||||
**Updated Message** (as of v1.2, see Issue #107881):
|
||||
```
|
||||
You need at least one 'Resolves: #<issue number>' line.
|
||||
```
|
||||
|
||||
**Location**: Line 218 in hook
|
||||
|
||||
**Important Context**:
|
||||
- The regex accepts both `Resolves:` and `Fixes:` for backward compatibility
|
||||
- TYPO3 community standard is to use ONLY `Resolves:`
|
||||
- The error message guides users toward the standard
|
||||
- This was the source of documentation confusion in Issue #105737
|
||||
|
||||
### 5. Releases Tag Validation
|
||||
|
||||
**Function**: `checkForReleases()`
|
||||
|
||||
**Rules**:
|
||||
- Every commit MUST have `Releases:` line
|
||||
- Format: `Releases: main, 13.4, 12.4` (comma-separated)
|
||||
- Valid values: `main`, version numbers like `13.4`, `12.4`
|
||||
|
||||
**Regex**: `/^Releases: (main|[0-9]+\.[0-9])(, *(main|[0-9]+\.[0-9]))*$/`
|
||||
|
||||
**Error Message**:
|
||||
```
|
||||
You need a 'Releases:' line. For instance: Releases: main, 8.7
|
||||
```
|
||||
|
||||
**Location**: Line 227 in hook
|
||||
|
||||
## Complete Validation Flow
|
||||
|
||||
```
|
||||
Commit attempted
|
||||
↓
|
||||
1. Check line length (≤ 72 chars)
|
||||
↓
|
||||
2. Check commit type ([BUGFIX], etc.)
|
||||
↓
|
||||
3. Check Resolves/Fixes tag exists
|
||||
↓
|
||||
4. Check Releases tag exists
|
||||
↓
|
||||
All pass? → Add Change-Id → Commit succeeds
|
||||
Any fail? → Show errors → Commit rejected
|
||||
```
|
||||
|
||||
## Error Messages
|
||||
|
||||
### Full Error Output
|
||||
|
||||
When validation fails:
|
||||
```
|
||||
------------------------------------------------------------------
|
||||
>> ERROR in your commit message:
|
||||
|
||||
- The maximum line length of 72 characters is exceeded.
|
||||
- You need at least one 'Resolves: #<issue number>' line.
|
||||
- You need a 'Releases:' line. For instance: Releases: main, 8.7
|
||||
|
||||
Please refer to [1] for details on the commit requirements.
|
||||
You should fix this and then do commit --amend etc.
|
||||
[1] https://docs.typo3.org/typo3cms/ContributionWorkflowGuide/latest/singlehtml/Index.html#commit-message-rules-for-typo3-cms
|
||||
------------------------------------------------------------------
|
||||
```
|
||||
|
||||
### Individual Errors
|
||||
|
||||
| Check | Error Message |
|
||||
|-------|---------------|
|
||||
| Line length | `The maximum line length of 72 characters is exceeded.` |
|
||||
| Commit type | `Your first line has to contain a commit type like '[BUGFIX]'.` |
|
||||
| Resolves tag | `You need at least one 'Resolves: #<issue number>' line.` |
|
||||
| Releases tag | `You need a 'Releases:' line. For instance: Releases: main, 8.7` |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Hook Not Running
|
||||
|
||||
**Symptom**: Commits succeed without validation
|
||||
|
||||
**Causes**:
|
||||
1. Hook not installed
|
||||
2. Hook not executable
|
||||
3. Git hooks disabled
|
||||
|
||||
**Solutions**:
|
||||
```bash
|
||||
# Check if hook exists
|
||||
ls -la .git/hooks/commit-msg
|
||||
|
||||
# Reinstall hook
|
||||
composer gerrit:setup
|
||||
|
||||
# Verify permissions
|
||||
chmod +x .git/hooks/commit-msg
|
||||
|
||||
# Check Git config (hooks disabled?)
|
||||
git config --get core.hooksPath
|
||||
```
|
||||
|
||||
### Hook Rejecting Valid Commit
|
||||
|
||||
**Symptom**: Valid commit message rejected
|
||||
|
||||
**Debug**:
|
||||
```bash
|
||||
# Test commit message manually
|
||||
bash .git/hooks/commit-msg .git/COMMIT_EDITMSG
|
||||
|
||||
# Check for hidden characters
|
||||
cat -A .git/COMMIT_EDITMSG
|
||||
|
||||
# Verify line endings (should be LF, not CRLF)
|
||||
file .git/COMMIT_EDITMSG
|
||||
```
|
||||
|
||||
**Common issues**:
|
||||
- Windows line endings (CRLF) instead of Unix (LF)
|
||||
- Trailing whitespace
|
||||
- Non-ASCII characters in unexpected places
|
||||
- Tabs vs spaces
|
||||
|
||||
### Change-Id Not Generated
|
||||
|
||||
**Symptom**: Commit succeeds but no Change-Id added
|
||||
|
||||
**Causes**:
|
||||
1. Change-Id already exists (manual addition)
|
||||
2. Config disables it: `git config --get gerrit.createChangeId` returns `false`
|
||||
3. fixup!/squash! commit (intentionally skipped)
|
||||
|
||||
**Solutions**:
|
||||
```bash
|
||||
# Enable Change-Id generation
|
||||
git config gerrit.createChangeId true
|
||||
|
||||
# Re-commit to generate
|
||||
git commit --amend --no-edit
|
||||
|
||||
# Verify Change-Id added
|
||||
git log -1
|
||||
```
|
||||
|
||||
### Multiple Change-Ids
|
||||
|
||||
**Symptom**: Commit has multiple Change-Id lines
|
||||
|
||||
**Impact**: Gerrit will reject or behave unexpectedly
|
||||
|
||||
**Fix**:
|
||||
```bash
|
||||
# Edit commit message
|
||||
git commit --amend
|
||||
|
||||
# Remove duplicate Change-Id lines (keep only one)
|
||||
# Save and exit
|
||||
```
|
||||
|
||||
## Hook Customization
|
||||
|
||||
### When to Customize
|
||||
|
||||
**Valid reasons**:
|
||||
- Project-specific validation rules
|
||||
- Additional required tags
|
||||
- Custom commit message format
|
||||
|
||||
**Invalid reasons**:
|
||||
- Bypassing validation (use `--no-verify` temporarily instead)
|
||||
- Making validation more lenient (breaks standardization)
|
||||
|
||||
### Safe Customization Pattern
|
||||
|
||||
```bash
|
||||
# 1. Fork the hook
|
||||
cp .git/hooks/commit-msg .git/hooks/commit-msg.custom
|
||||
|
||||
# 2. Add custom validation
|
||||
# Edit .git/hooks/commit-msg.custom
|
||||
|
||||
# 3. Call from main hook
|
||||
# In .git/hooks/commit-msg, add:
|
||||
# .git/hooks/commit-msg.custom "$1"
|
||||
|
||||
# 4. Document customization
|
||||
echo "Custom validation: <description>" >> .git/hooks/commit-msg.custom
|
||||
```
|
||||
|
||||
### Example: Additional Tag
|
||||
|
||||
Add optional `Sponsored-by:` tag:
|
||||
|
||||
```bash
|
||||
# Add to checkForResolves() section
|
||||
checkForSponsor() {
|
||||
if grep -q -E '^Sponsored-by: ' "$MSG"; then
|
||||
# Valid sponsor tag found
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# Call in validation sequence
|
||||
checkForLineLength
|
||||
checkForCommitType
|
||||
checkForResolves
|
||||
checkForReleases
|
||||
checkForSponsor # Custom check
|
||||
```
|
||||
|
||||
## Bypassing Hook
|
||||
|
||||
### When to Bypass
|
||||
|
||||
**Valid cases**:
|
||||
- Emergency hotfixes
|
||||
- Rebasing with preserved commits
|
||||
- Importing historical commits
|
||||
- Temporary testing
|
||||
|
||||
**Invalid cases**:
|
||||
- Avoiding fixing commit message
|
||||
- Regular development workflow
|
||||
|
||||
### How to Bypass
|
||||
|
||||
```bash
|
||||
# Single commit
|
||||
git commit --no-verify
|
||||
|
||||
# Amend without hook
|
||||
git commit --amend --no-verify
|
||||
|
||||
# Rebase without hook
|
||||
GIT_EDITOR=true git rebase -i HEAD~5 --no-verify
|
||||
```
|
||||
|
||||
**Warning**: Gerrit will still reject invalid commits! Bypassing hook locally doesn't bypass Gerrit validation.
|
||||
|
||||
## Hook History
|
||||
|
||||
### Version 1.1 (Current)
|
||||
|
||||
**From**: TYPO3 CI Review 1.1
|
||||
**Based on**: Gerrit Code Review 2.14.6
|
||||
|
||||
**Changes from Gerrit original**:
|
||||
- Added line length check (72 chars)
|
||||
- Added commit type check ([BUGFIX], etc.)
|
||||
- Added Resolves/Fixes check
|
||||
- Added Releases check
|
||||
- Modified Change-Id placement (after footer)
|
||||
|
||||
### Proposed Version 1.2 (Issue #107881)
|
||||
|
||||
**Change**: Update error message for Resolves check
|
||||
- **Old**: `'Resolves|Fixes: #<issue number>'`
|
||||
- **New**: `'Resolves: #<issue number>'`
|
||||
|
||||
**Rationale**:
|
||||
- TYPO3 standard is `Resolves:` only
|
||||
- `Fixes:` accepted for backward compatibility only
|
||||
- Error message should guide toward standard practice
|
||||
|
||||
**Validation regex unchanged**: Still accepts both for compatibility
|
||||
|
||||
## Hook Source Code Structure
|
||||
|
||||
```bash
|
||||
Build/git-hooks/commit-msg
|
||||
├── License header (Apache 2.0)
|
||||
├── TYPO3 changes documentation
|
||||
├── add_ChangeId() function
|
||||
│ ├── clean_message preprocessing
|
||||
│ ├── Skip conditions (fixup, squash, existing)
|
||||
│ ├── ID generation
|
||||
│ └── AWK script for placement
|
||||
├── _gen_ChangeId() helper
|
||||
├── _gen_ChangeIdInput() helper
|
||||
├── Validation functions:
|
||||
│ ├── checkForLineLength()
|
||||
│ ├── checkForCommitType()
|
||||
│ ├── checkForResolves()
|
||||
│ └── checkForReleases()
|
||||
├── Validation execution
|
||||
└── add_ChangeId() call
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### For Contributors
|
||||
|
||||
1. **Install hook early**: First thing after cloning
|
||||
2. **Never bypass**: Fix messages instead of bypassing
|
||||
3. **Don't fight the hook**: Learn requirements, follow them
|
||||
4. **Use templates**: `git config commit.template ~/.gitmessage.txt`
|
||||
5. **Test locally**: Use `scripts/validate-commit-message.py`
|
||||
|
||||
### For Maintainers
|
||||
|
||||
1. **Document changes**: Keep this reference updated
|
||||
2. **Test thoroughly**: Validate changes don't break existing commits
|
||||
3. **Backward compatible**: Keep regex accepting old patterns
|
||||
4. **Clear errors**: Make error messages actionable
|
||||
5. **Version carefully**: Changes affect all contributors
|
||||
|
||||
## Related Files
|
||||
|
||||
- `Build/git-hooks/commit-msg` - The hook itself
|
||||
- `Build/git-hooks/pre-commit` - Code quality checks
|
||||
- `assets/commit-template.txt` - Commit message template
|
||||
- `scripts/validate-commit-message.py` - Offline validator
|
||||
- `references/commit-message-format.md` - Commit message specification
|
||||
|
||||
## References
|
||||
|
||||
- **Hook Source**: https://github.com/TYPO3/typo3/blob/main/Build/git-hooks/commit-msg
|
||||
- **Git Hooks Docs**: https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks
|
||||
- **Gerrit Hooks**: https://gerrit-review.googlesource.com/Documentation/cmd-hook-commit-msg.html
|
||||
- **Issue #107881**: Standardize error message to mention only Resolves
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Validation | Required Format | Error if Missing |
|
||||
|------------|----------------|------------------|
|
||||
| Commit type | `[TYPE]` in first line | Yes |
|
||||
| Line length | ≤ 72 characters | Yes |
|
||||
| Resolves | `Resolves: #123` | Yes |
|
||||
| Releases | `Releases: main` | Yes |
|
||||
| Change-Id | Auto-generated | N/A (added by hook) |
|
||||
|
||||
## See Also
|
||||
|
||||
- `scripts/validate-commit-message.py` - Test messages offline
|
||||
- `scripts/create-commit-message.py` - Generate compliant messages
|
||||
- `assets/commit-template.txt` - Pre-filled template
|
||||
@@ -0,0 +1,674 @@
|
||||
# TYPO3 Core Development - Complete DDEV Setup Workflow
|
||||
|
||||
Production-tested workflow for setting up a complete TYPO3 Core development environment with DDEV.
|
||||
|
||||
## Overview
|
||||
|
||||
This workflow creates a fully functional TYPO3 Core development environment with:
|
||||
- TYPO3 Core v14 (main branch)
|
||||
- PHP 8.4 on Apache with FPM
|
||||
- MariaDB 10.6
|
||||
- Test data and styleguide extensions
|
||||
- Git configured for Gerrit submissions
|
||||
- Ready for Core development and testing
|
||||
|
||||
## Prerequisites
|
||||
|
||||
**Required**:
|
||||
- Git installed and configured
|
||||
- DDEV installed (https://ddev.readthedocs.io/)
|
||||
- SSH keys configured for GitHub and Gerrit
|
||||
|
||||
**Verify DDEV**:
|
||||
```bash
|
||||
ddev version
|
||||
# Should show DDEV version >= 1.21
|
||||
```
|
||||
|
||||
## Complete Setup Workflow
|
||||
|
||||
### Step 1: Create Project Directory
|
||||
|
||||
```bash
|
||||
# Option A: Using 'take' (zsh/oh-my-zsh)
|
||||
take t3coredev-14-php8-4
|
||||
|
||||
# Option B: Standard bash
|
||||
mkdir -p t3coredev-14-php8-4 && cd t3coredev-14-php8-4
|
||||
```
|
||||
|
||||
**Note**: Use descriptive directory names indicating TYPO3 version and PHP version.
|
||||
|
||||
### Step 2: Clone TYPO3 Core Repository
|
||||
|
||||
```bash
|
||||
# Clone from GitHub (faster than Gerrit for initial clone)
|
||||
git clone git@github.com:typo3/typo3 .
|
||||
|
||||
# Note: The dot (.) clones into current directory
|
||||
```
|
||||
|
||||
### Step 3: Configure Git for TYPO3 Contributions
|
||||
|
||||
```bash
|
||||
# Set your identity
|
||||
git config user.name "YOUR NAME"
|
||||
git config user.email "YOUR@EMAIL"
|
||||
|
||||
# Enable automatic rebase (required for TYPO3)
|
||||
git config branch.autosetuprebase remote
|
||||
```
|
||||
|
||||
### Step 4: Install Git Hooks
|
||||
|
||||
```bash
|
||||
# Copy commit-msg hook (adds Change-Id)
|
||||
cp Build/git-hooks/commit-msg .git/hooks/commit-msg
|
||||
|
||||
# Alternative: Use composer command
|
||||
# composer gerrit:setup
|
||||
```
|
||||
|
||||
### Step 5: Configure Gerrit Remote
|
||||
|
||||
```bash
|
||||
# Set Gerrit as push destination
|
||||
git config remote.origin.pushurl ssh://YOURT3OUSERNAME@review.typo3.org:29418/Packages/TYPO3.CMS.git
|
||||
|
||||
# Configure push refspec for Gerrit review
|
||||
git config remote.origin.push +refs/heads/main:refs/for/main
|
||||
```
|
||||
|
||||
**Important**: Replace `YOURT3OUSERNAME` with your actual Gerrit username!
|
||||
|
||||
### Step 6: Configure DDEV Project
|
||||
|
||||
```bash
|
||||
# Set project type to TYPO3
|
||||
ddev config --project-type typo3 -y
|
||||
|
||||
# Configure timezone (adjust to your location)
|
||||
ddev config --timezone "Europe/Vienna"
|
||||
|
||||
# Set PHP version for v14 development
|
||||
ddev config --php-version=8.4
|
||||
|
||||
# Use Apache with FPM (recommended for Core dev)
|
||||
ddev config --webserver-type=apache-fpm
|
||||
|
||||
# Set MariaDB version
|
||||
ddev config --database=mariadb:10.6
|
||||
```
|
||||
|
||||
**PHP Version Notes**:
|
||||
- TYPO3 v14: PHP 8.2, 8.3, 8.4
|
||||
- TYPO3 v13: PHP 8.1, 8.2, 8.3
|
||||
- Check `composer.json` for exact requirements
|
||||
|
||||
### Step 7: Configure DDEV Environment Variables
|
||||
|
||||
```bash
|
||||
# Set TYPO3 context to Development/Ddev
|
||||
ddev config --web-environment-add="TYPO3_CONTEXT=Development/Ddev"
|
||||
|
||||
# Set Composer root version for dev branch
|
||||
ddev config --web-environment-add="COMPOSER_ROOT_VERSION=14.0.x-dev"
|
||||
```
|
||||
|
||||
**Context Meanings**:
|
||||
- `Development/Ddev`: Enables debugging, disables caching
|
||||
- `Production`: Live site configuration
|
||||
- `Testing`: For automated test environments
|
||||
|
||||
### Step 8: Start DDEV
|
||||
|
||||
```bash
|
||||
ddev start
|
||||
```
|
||||
|
||||
**What happens**:
|
||||
1. Creates Docker containers (web, db, phpmyadmin)
|
||||
2. Configures networking
|
||||
3. Sets up SSL certificates
|
||||
4. Mounts project directory
|
||||
|
||||
**Expected output**:
|
||||
```
|
||||
Starting t3coredev-14-php8-4...
|
||||
Successfully started t3coredev-14-php8-4
|
||||
Project can be reached at https://t3coredev-14-php8-4.ddev.site
|
||||
```
|
||||
|
||||
### Step 9: Install Dependencies
|
||||
|
||||
```bash
|
||||
# Use TYPO3's runTests.sh script (preferred for Core dev)
|
||||
./Build/Scripts/runTests.sh -s composerInstall
|
||||
|
||||
# Alternative: Direct composer command
|
||||
# ddev composer install
|
||||
```
|
||||
|
||||
**Why runTests.sh?**
|
||||
- Ensures correct Composer flags
|
||||
- Consistent with CI environment
|
||||
- Handles Core-specific requirements
|
||||
|
||||
### Step 10: Prepare TYPO3 Installation
|
||||
|
||||
```bash
|
||||
# Create installation trigger file
|
||||
ddev exec 'touch /var/www/html/FIRST_INSTALL'
|
||||
|
||||
# Enable Install Tool
|
||||
ddev exec 'touch /var/www/html/typo3conf/ENABLE_INSTALL_TOOL'
|
||||
ddev exec 'echo "KEEP_FILE" > /var/www/html/typo3conf/ENABLE_INSTALL_TOOL'
|
||||
```
|
||||
|
||||
**File purposes**:
|
||||
- `FIRST_INSTALL`: Triggers installation wizard
|
||||
- `ENABLE_INSTALL_TOOL`: Enables Install Tool access (with KEEP_FILE prevents auto-deletion)
|
||||
|
||||
### Step 11: Run TYPO3 Setup
|
||||
|
||||
```bash
|
||||
ddev typo3 setup \
|
||||
--driver=mysqli \
|
||||
--host=db \
|
||||
--port=3306 \
|
||||
--dbname=db \
|
||||
--username=db \
|
||||
--password=db \
|
||||
--admin-username=backenduser \
|
||||
--admin-user-password='YOUR_SECURE_PASSWORD' \
|
||||
--admin-email='YOUR@EMAIL' \
|
||||
--project-name='TYPO3 Core Dev v14 PHP 8.4' \
|
||||
--no-interaction \
|
||||
--server-type=apache \
|
||||
--force
|
||||
```
|
||||
|
||||
**Important**:
|
||||
- Replace `YOUR_SECURE_PASSWORD` with your preferred admin password
|
||||
- Replace `YOUR@EMAIL` with your email
|
||||
- Database credentials (db/db/db) are DDEV defaults
|
||||
|
||||
**What this creates**:
|
||||
- Database tables and schema
|
||||
- Backend admin user account
|
||||
- Basic TYPO3 configuration
|
||||
- AdditionalConfiguration.php
|
||||
|
||||
### Step 12: Activate Core Extensions
|
||||
|
||||
```bash
|
||||
# Set up extensions first
|
||||
ddev typo3 extension:setup
|
||||
|
||||
# Activate indexed_search (relevant for testing search functionality)
|
||||
ddev typo3 extension:activate indexed_search
|
||||
|
||||
# Activate styleguide (provides test data and UI components)
|
||||
ddev typo3 extension:activate styleguide
|
||||
|
||||
# Activate scheduler (for scheduled tasks)
|
||||
ddev typo3 extension:activate scheduler
|
||||
```
|
||||
|
||||
**Extension purposes**:
|
||||
- `indexed_search`: Full-text search (relevant to bug #105737!)
|
||||
- `styleguide`: Test data generator, UI component showcase
|
||||
- `scheduler`: Cron-like task scheduling
|
||||
|
||||
### Step 13: Configure Backend User Groups
|
||||
|
||||
```bash
|
||||
ddev typo3 setup:begroups:default --groups=Both
|
||||
```
|
||||
|
||||
**Creates**:
|
||||
- Editor group (content management)
|
||||
- Advanced Editor group (extended permissions)
|
||||
- Assigns both groups to admin user
|
||||
|
||||
### Step 14: Generate Test Data
|
||||
|
||||
```bash
|
||||
# Generate TCA (Table Configuration Array) examples
|
||||
ddev typo3 styleguide:generate --create -- tca
|
||||
|
||||
# Generate frontend system template
|
||||
ddev typo3 styleguide:generate --create -- frontend-systemplate
|
||||
```
|
||||
|
||||
**Test data includes**:
|
||||
- All TCA field types with examples
|
||||
- Content elements with various configurations
|
||||
- Pages with different properties
|
||||
- Frontend templates and TypoScript
|
||||
|
||||
### Step 15: Launch TYPO3 Backend
|
||||
|
||||
```bash
|
||||
ddev launch /typo3
|
||||
```
|
||||
|
||||
Opens TYPO3 backend in your default browser.
|
||||
|
||||
**Login credentials**:
|
||||
- Username: `backenduser`
|
||||
- Password: Whatever you set in Step 11
|
||||
|
||||
## Post-Setup Verification
|
||||
|
||||
### Verify Installation
|
||||
|
||||
**Check TYPO3 is running**:
|
||||
```bash
|
||||
ddev launch
|
||||
```
|
||||
|
||||
**Access Install Tool**:
|
||||
```bash
|
||||
ddev launch /typo3/install.php
|
||||
```
|
||||
|
||||
**View site info**:
|
||||
```bash
|
||||
ddev describe
|
||||
```
|
||||
|
||||
### Verify Git Configuration
|
||||
|
||||
```bash
|
||||
# Check user config
|
||||
git config user.name
|
||||
git config user.email
|
||||
|
||||
# Check Gerrit config
|
||||
git config remote.origin.pushurl
|
||||
git config remote.origin.push
|
||||
|
||||
# Verify hooks
|
||||
ls -la .git/hooks/commit-msg
|
||||
```
|
||||
|
||||
### Verify DDEV Configuration
|
||||
|
||||
```bash
|
||||
# View DDEV config
|
||||
cat .ddev/config.yaml
|
||||
|
||||
# Should show:
|
||||
# - project_type: typo3
|
||||
# - php_version: "8.4"
|
||||
# - webserver_type: apache-fpm
|
||||
# - database: mariadb:10.6
|
||||
# - web_environment:
|
||||
# - TYPO3_CONTEXT=Development/Ddev
|
||||
# - COMPOSER_ROOT_VERSION=14.0.x-dev
|
||||
```
|
||||
|
||||
### Test Core Functionality
|
||||
|
||||
**Access frontend**:
|
||||
```bash
|
||||
ddev launch
|
||||
```
|
||||
|
||||
**Run tests**:
|
||||
```bash
|
||||
# Unit tests
|
||||
./Build/Scripts/runTests.sh -s unit
|
||||
|
||||
# Functional tests
|
||||
./Build/Scripts/runTests.sh -s functional
|
||||
|
||||
# Check available test suites
|
||||
./Build/Scripts/runTests.sh -h
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Creating a Feature Branch
|
||||
|
||||
```bash
|
||||
# Ensure main is up-to-date
|
||||
git checkout main
|
||||
git pull origin main
|
||||
|
||||
# Create feature branch
|
||||
git checkout -b feature/105737-fix-indexed-search-crash
|
||||
```
|
||||
|
||||
### Making Changes
|
||||
|
||||
```bash
|
||||
# Make code changes
|
||||
vim typo3/sysext/indexed_search/Classes/Indexer.php
|
||||
|
||||
# Stage changes
|
||||
git add .
|
||||
|
||||
# Commit with proper message
|
||||
git commit
|
||||
# (Use commit message template)
|
||||
```
|
||||
|
||||
### Testing Changes
|
||||
|
||||
```bash
|
||||
# Run relevant tests
|
||||
./Build/Scripts/runTests.sh -s unit -- \
|
||||
typo3/sysext/indexed_search/Tests/Unit/
|
||||
|
||||
# Check code style
|
||||
./Build/Scripts/runTests.sh -s cgl -n
|
||||
```
|
||||
|
||||
### Submitting to Gerrit
|
||||
|
||||
```bash
|
||||
# Push to Gerrit for review
|
||||
git push origin HEAD:refs/for/main
|
||||
```
|
||||
|
||||
## Useful DDEV Commands
|
||||
|
||||
### Project Management
|
||||
|
||||
```bash
|
||||
# Start project
|
||||
ddev start
|
||||
|
||||
# Stop project
|
||||
ddev stop
|
||||
|
||||
# Restart project
|
||||
ddev restart
|
||||
|
||||
# Delete project (keeps files)
|
||||
ddev delete
|
||||
|
||||
# Power off all DDEV projects
|
||||
ddev poweroff
|
||||
```
|
||||
|
||||
### Database Management
|
||||
|
||||
```bash
|
||||
# Export database
|
||||
ddev export-db --file=backup.sql.gz
|
||||
|
||||
# Import database
|
||||
ddev import-db --file=backup.sql.gz
|
||||
|
||||
# Access database CLI
|
||||
ddev mysql
|
||||
|
||||
# Launch phpMyAdmin
|
||||
ddev launch -p
|
||||
```
|
||||
|
||||
### TYPO3 Commands
|
||||
|
||||
```bash
|
||||
# Clear all caches
|
||||
ddev typo3 cache:flush
|
||||
|
||||
# Clear specific cache
|
||||
ddev typo3 cache:flush --group=system
|
||||
|
||||
# Run scheduler tasks
|
||||
ddev typo3 scheduler:run
|
||||
|
||||
# List available commands
|
||||
ddev typo3 list
|
||||
```
|
||||
|
||||
### Debugging
|
||||
|
||||
```bash
|
||||
# View logs
|
||||
ddev logs
|
||||
|
||||
# Follow logs (like tail -f)
|
||||
ddev logs -f
|
||||
|
||||
# SSH into container
|
||||
ddev ssh
|
||||
|
||||
# Execute command in container
|
||||
ddev exec 'command'
|
||||
```
|
||||
|
||||
### Performance
|
||||
|
||||
```bash
|
||||
# View resource usage
|
||||
docker stats
|
||||
|
||||
# Restart services if slow
|
||||
ddev restart
|
||||
```
|
||||
|
||||
## Customization Options
|
||||
|
||||
### Different PHP Versions
|
||||
|
||||
```bash
|
||||
# Switch to PHP 8.2
|
||||
ddev config --php-version=8.2
|
||||
ddev restart
|
||||
```
|
||||
|
||||
### Different Database Versions
|
||||
|
||||
```bash
|
||||
# Use MySQL instead of MariaDB
|
||||
ddev config --database=mysql:8.0
|
||||
ddev restart
|
||||
```
|
||||
|
||||
### Additional Services
|
||||
|
||||
```bash
|
||||
# Add Redis
|
||||
ddev get ddev/ddev-redis
|
||||
|
||||
# Add Elasticsearch
|
||||
ddev get ddev/ddev-elasticsearch
|
||||
|
||||
# Add Mailhog (email testing)
|
||||
ddev config --mailhog-port=8026
|
||||
```
|
||||
|
||||
### Custom Domain
|
||||
|
||||
```bash
|
||||
# Add additional hostname
|
||||
ddev config --additional-hostnames=t3dev.local
|
||||
ddev restart
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Port already allocated"
|
||||
|
||||
**Problem**: DDEV can't start because ports are in use
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# Stop other DDEV projects
|
||||
ddev poweroff
|
||||
|
||||
# Or change port
|
||||
ddev config --router-http-port=8080 --router-https-port=8443
|
||||
```
|
||||
|
||||
### "Composer timeout"
|
||||
|
||||
**Problem**: Composer operations timeout
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# Increase timeout
|
||||
ddev composer config --global process-timeout 2000
|
||||
|
||||
# Or use runTests.sh
|
||||
./Build/Scripts/runTests.sh -s composerInstall
|
||||
```
|
||||
|
||||
### "Cannot write to directory"
|
||||
|
||||
**Problem**: Permission issues in container
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# Fix permissions
|
||||
ddev exec 'chmod -R 777 var/ typo3temp/ typo3conf/'
|
||||
|
||||
# Or restart DDEV
|
||||
ddev restart
|
||||
```
|
||||
|
||||
### "Database connection failed"
|
||||
|
||||
**Problem**: TYPO3 can't connect to database
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# Check database is running
|
||||
ddev describe
|
||||
|
||||
# Verify credentials in LocalConfiguration.php
|
||||
ddev exec 'cat typo3conf/LocalConfiguration.php | grep -A5 DB'
|
||||
|
||||
# Should show: host=db, username=db, password=db, database=db
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Directory Naming
|
||||
|
||||
Use descriptive names indicating:
|
||||
- TYPO3 version: `t3coredev-14`
|
||||
- PHP version: `php8-4`
|
||||
- Purpose: `coredev`, `testing`, `feature-name`
|
||||
|
||||
Examples:
|
||||
- `t3coredev-14-php8-4`
|
||||
- `t3-bugfix-105737`
|
||||
- `t3-testing-indexed-search`
|
||||
|
||||
### Git Workflow
|
||||
|
||||
1. **Always start from main**:
|
||||
```bash
|
||||
git checkout main
|
||||
git pull origin main
|
||||
```
|
||||
|
||||
2. **Use feature branches**:
|
||||
```bash
|
||||
git checkout -b feature/issue-description
|
||||
```
|
||||
|
||||
3. **Keep single commit per patch**:
|
||||
```bash
|
||||
git commit --amend # Update existing commit
|
||||
```
|
||||
|
||||
4. **Rebase regularly**:
|
||||
```bash
|
||||
git fetch origin
|
||||
git rebase origin/main
|
||||
```
|
||||
|
||||
### DDEV Management
|
||||
|
||||
1. **Stop unused projects**:
|
||||
```bash
|
||||
ddev list
|
||||
ddev stop <project>
|
||||
```
|
||||
|
||||
2. **Clean up old projects**:
|
||||
```bash
|
||||
ddev delete <project>
|
||||
# Then manually delete directory
|
||||
```
|
||||
|
||||
3. **Monitor resources**:
|
||||
```bash
|
||||
docker stats
|
||||
```
|
||||
|
||||
### Testing Workflow
|
||||
|
||||
1. **Test before committing**:
|
||||
```bash
|
||||
./Build/Scripts/runTests.sh -s unit
|
||||
./Build/Scripts/runTests.sh -s functional
|
||||
```
|
||||
|
||||
2. **Check code style**:
|
||||
```bash
|
||||
./Build/Scripts/runTests.sh -s cgl -n
|
||||
```
|
||||
|
||||
3. **Fix code style automatically**:
|
||||
```bash
|
||||
./Build/Scripts/runTests.sh -s cgl
|
||||
```
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Essential Commands
|
||||
|
||||
| Task | Command |
|
||||
|------|---------|
|
||||
| Start DDEV | `ddev start` |
|
||||
| Stop DDEV | `ddev stop` |
|
||||
| Open backend | `ddev launch /typo3` |
|
||||
| Clear cache | `ddev typo3 cache:flush` |
|
||||
| Run unit tests | `./Build/Scripts/runTests.sh -s unit` |
|
||||
| Install composer | `./Build/Scripts/runTests.sh -s composerInstall` |
|
||||
| View logs | `ddev logs -f` |
|
||||
| SSH into container | `ddev ssh` |
|
||||
| Export database | `ddev export-db --file=backup.sql.gz` |
|
||||
| Git push to Gerrit | `git push origin HEAD:refs/for/main` |
|
||||
|
||||
### TYPO3 Versions & PHP Compatibility
|
||||
|
||||
| TYPO3 Version | PHP Versions | Branch | Status |
|
||||
|---------------|--------------|--------|--------|
|
||||
| v14 (main) | 8.2, 8.3, 8.4 | main | Development |
|
||||
| v13 (LTS) | 8.1, 8.2, 8.3 | 13.4 | Active |
|
||||
| v12 (ELTS) | 8.1, 8.2 | 12.4 | Security only |
|
||||
|
||||
### Default Credentials
|
||||
|
||||
| Service | URL | Username | Password |
|
||||
|---------|-----|----------|----------|
|
||||
| Backend | `https://[project].ddev.site/typo3` | backenduser | (your choice) |
|
||||
| Database | `db:3306` | db | db |
|
||||
| phpMyAdmin | `https://[project].ddev.site:8037` | db | db |
|
||||
|
||||
## Integration with typo3-ddev-skill
|
||||
|
||||
This workflow complements the `typo3-ddev-skill`:
|
||||
- Use `typo3-ddev-skill` for quick setup automation
|
||||
- Use this workflow for manual step-by-step understanding
|
||||
- Both produce equivalent development environments
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- **DDEV Documentation**: https://ddev.readthedocs.io/
|
||||
- **TYPO3 Development**: https://docs.typo3.org/m/typo3/reference-coreapi/
|
||||
- **runTests.sh Guide**: https://docs.typo3.org/m/typo3/guide-contributionworkflow/main/en-us/Testing/
|
||||
- **TYPO3 Slack**: https://typo3.slack.com (#typo3-cms-coredev)
|
||||
|
||||
---
|
||||
|
||||
**Note**: This workflow is based on proven production usage and is continuously updated for current TYPO3 versions. Always check official documentation for the latest recommendations.
|
||||
436
skills/typo3-core-contributions/references/forge-api.md
Normal file
436
skills/typo3-core-contributions/references/forge-api.md
Normal file
@@ -0,0 +1,436 @@
|
||||
# Forge REST API Documentation
|
||||
|
||||
Complete guide to using the TYPO3 Forge (Redmine) REST API for programmatic issue management.
|
||||
|
||||
## Overview
|
||||
|
||||
TYPO3 Forge (https://forge.typo3.org) is built on Redmine and exposes a REST API for:
|
||||
- Creating issues
|
||||
- Updating issues
|
||||
- Querying project metadata
|
||||
- Managing issue relationships
|
||||
|
||||
## Authentication
|
||||
|
||||
### Get API Key
|
||||
|
||||
1. Log in to https://forge.typo3.org
|
||||
2. Go to https://forge.typo3.org/my/account
|
||||
3. Find "API access key" on the right side
|
||||
4. Click "Show" to reveal your key
|
||||
5. Store securely (treat like a password!)
|
||||
|
||||
### Using API Key
|
||||
|
||||
Pass via HTTP header:
|
||||
```bash
|
||||
-H "X-Redmine-API-Key: your-api-key-here"
|
||||
```
|
||||
|
||||
**Security**: Never commit API keys to repositories. Use environment variables:
|
||||
```bash
|
||||
export FORGE_API_KEY="your-api-key-here"
|
||||
```
|
||||
|
||||
## Base URL
|
||||
|
||||
All API endpoints use:
|
||||
```
|
||||
https://forge.typo3.org
|
||||
```
|
||||
|
||||
## Common Endpoints
|
||||
|
||||
### Create Issue
|
||||
|
||||
**Endpoint**: `POST /issues.json`
|
||||
|
||||
**Request**:
|
||||
```bash
|
||||
curl -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Redmine-API-Key: $FORGE_API_KEY" \
|
||||
-d '{
|
||||
"issue": {
|
||||
"project_id": "typo3cms-core",
|
||||
"subject": "Issue title here",
|
||||
"description": "Detailed description",
|
||||
"tracker_id": 1,
|
||||
"category_id": 975,
|
||||
"priority_id": 4,
|
||||
"custom_fields": [
|
||||
{"id": 4, "value": "13"}
|
||||
]
|
||||
}
|
||||
}' \
|
||||
https://forge.typo3.org/issues.json
|
||||
```
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"issue": {
|
||||
"id": 107881,
|
||||
"project": {"id": 27, "name": "TYPO3 Core"},
|
||||
"tracker": {"id": 1, "name": "Bug"},
|
||||
"status": {"id": 1, "name": "New"},
|
||||
"priority": {"id": 4, "name": "Should have"},
|
||||
"subject": "Issue title here",
|
||||
"description": "Detailed description",
|
||||
"created_on": "2024-12-15T10:30:00Z",
|
||||
"updated_on": "2024-12-15T10:30:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Extract Issue Number**:
|
||||
```bash
|
||||
# Parse with jq
|
||||
curl ... | jq -r '.issue.id'
|
||||
|
||||
# Parse with grep
|
||||
curl ... | grep -oP '"id":\K[0-9]+' | head -1
|
||||
```
|
||||
|
||||
### Get Project Metadata
|
||||
|
||||
**Endpoint**: `GET /projects/typo3cms-core.json`
|
||||
|
||||
**Request**:
|
||||
```bash
|
||||
curl -H "X-Redmine-API-Key: $FORGE_API_KEY" \
|
||||
https://forge.typo3.org/projects/typo3cms-core.json
|
||||
```
|
||||
|
||||
**Response includes**:
|
||||
- Available trackers (Bug, Feature, Task, etc.)
|
||||
- Issue categories (Backend, Frontend, etc.)
|
||||
- Custom field definitions
|
||||
|
||||
### Get Issue Details
|
||||
|
||||
**Endpoint**: `GET /issues/{id}.json`
|
||||
|
||||
**Request**:
|
||||
```bash
|
||||
curl -H "X-Redmine-API-Key: $FORGE_API_KEY" \
|
||||
https://forge.typo3.org/issues/105737.json
|
||||
```
|
||||
|
||||
**Response includes**:
|
||||
- Full issue details
|
||||
- Custom fields
|
||||
- Status and assignments
|
||||
- Related issues
|
||||
|
||||
## Field IDs
|
||||
|
||||
### Trackers
|
||||
|
||||
| ID | Name |
|
||||
|----|------|
|
||||
| 1 | Bug |
|
||||
| 2 | Feature |
|
||||
| 4 | Task |
|
||||
| 6 | Story |
|
||||
| 10 | Epic |
|
||||
|
||||
### Priorities
|
||||
|
||||
| ID | Name |
|
||||
|----|------|
|
||||
| 2 | Nice to have |
|
||||
| 3 | Must have |
|
||||
| 4 | Should have |
|
||||
| 5 | Could have |
|
||||
|
||||
### Common Categories
|
||||
|
||||
| ID | Name |
|
||||
|------|------|
|
||||
| 971 | Backend API |
|
||||
| 972 | Backend User Interface |
|
||||
| 973 | Caching |
|
||||
| 974 | Database API (Doctrine DBAL) |
|
||||
| 975 | Miscellaneous |
|
||||
| 976 | Extension Manager |
|
||||
| 977 | Frontend |
|
||||
| 1000 | Indexed Search |
|
||||
| 1003 | Content Rendering |
|
||||
| 1004 | Documentation |
|
||||
|
||||
**Get full list**: Use `scripts/query-forge-metadata.sh`
|
||||
|
||||
### Custom Fields
|
||||
|
||||
| ID | Name | Purpose |
|
||||
|----|------|---------|
|
||||
| 3 | Tags | Comma-separated keywords |
|
||||
| 4 | TYPO3 Version | Version affected (e.g., "13", "12") |
|
||||
| 5 | PHP Version | PHP version (e.g., "8.2", "8.3") |
|
||||
| 8 | Complexity | Complexity estimate |
|
||||
| 15 | Is Regression | Whether it's a regression |
|
||||
| 18 | Sprint Focus | Sprint assignment |
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Example 1: Create Bug Report
|
||||
|
||||
```bash
|
||||
export FORGE_API_KEY="your-api-key-here"
|
||||
|
||||
curl -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Redmine-API-Key: $FORGE_API_KEY" \
|
||||
-d '{
|
||||
"issue": {
|
||||
"project_id": "typo3cms-core",
|
||||
"subject": "Indexed search causes crash on malformed UTF-8",
|
||||
"description": "When processing content with malformed UTF-8, the indexed search indexer crashes with TypeError in PHP 8.2+.\n\nSteps to reproduce:\n1. Create page with malformed UTF-8 content\n2. Run indexer\n3. Observe crash\n\nExpected: Graceful handling\nActual: TypeError exception",
|
||||
"tracker_id": 1,
|
||||
"category_id": 1000,
|
||||
"priority_id": 4,
|
||||
"custom_fields": [
|
||||
{"id": 4, "value": "13"},
|
||||
{"id": 5, "value": "8.2"},
|
||||
{"id": 3, "value": "indexed search, UTF-8, crash"}
|
||||
]
|
||||
}
|
||||
}' \
|
||||
https://forge.typo3.org/issues.json
|
||||
```
|
||||
|
||||
### Example 2: Create Feature Request
|
||||
|
||||
```bash
|
||||
curl -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Redmine-API-Key: $FORGE_API_KEY" \
|
||||
-d '{
|
||||
"issue": {
|
||||
"project_id": "typo3cms-core",
|
||||
"subject": "Add WebP image format support",
|
||||
"description": "Add native WebP support to TYPO3 image processing:\n\n- WebP MIME type detection\n- Image manipulation support\n- Configuration options\n\nBenefit: 25-30% better compression than JPEG",
|
||||
"tracker_id": 2,
|
||||
"category_id": 977,
|
||||
"priority_id": 5,
|
||||
"custom_fields": [
|
||||
{"id": 4, "value": "14"},
|
||||
{"id": 3, "value": "WebP, images, performance"}
|
||||
]
|
||||
}
|
||||
}' \
|
||||
https://forge.typo3.org/issues.json
|
||||
```
|
||||
|
||||
### Example 3: Create Task
|
||||
|
||||
```bash
|
||||
curl -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Redmine-API-Key: $FORGE_API_KEY" \
|
||||
-d '{
|
||||
"issue": {
|
||||
"project_id": "typo3cms-core",
|
||||
"subject": "Standardize commit-msg hook error message",
|
||||
"description": "Update Build/git-hooks/commit-msg error message to mention only '\''Resolves:'\'' tag instead of '\''Resolves|Fixes:'\'' to align with TYPO3 community standard.\n\nWhile validation regex accepts both for backward compatibility, error message should guide toward single standard keyword.",
|
||||
"tracker_id": 4,
|
||||
"category_id": 975,
|
||||
"priority_id": 4,
|
||||
"custom_fields": [
|
||||
{"id": 4, "value": "14"}
|
||||
]
|
||||
}
|
||||
}' \
|
||||
https://forge.typo3.org/issues.json
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Common Errors
|
||||
|
||||
**422 Unprocessable Entity**:
|
||||
```json
|
||||
{
|
||||
"errors": ["Category is not included in the list"]
|
||||
}
|
||||
```
|
||||
|
||||
**Solution**: Check field IDs are valid for the project
|
||||
|
||||
**422 - Required Field Missing**:
|
||||
```json
|
||||
{
|
||||
"errors": ["Typo3 version cannot be blank"]
|
||||
}
|
||||
```
|
||||
|
||||
**Solution**: Add required custom field (id: 4 for TYPO3 version)
|
||||
|
||||
**401 Unauthorized**:
|
||||
```
|
||||
{"errors": ["You are not authorized to access this page."]}
|
||||
```
|
||||
|
||||
**Solution**: Check API key is correct and has permissions
|
||||
|
||||
### Validation
|
||||
|
||||
Before creating issue, validate:
|
||||
- [ ] API key is set and valid
|
||||
- [ ] project_id is "typo3cms-core"
|
||||
- [ ] subject is descriptive (not too generic)
|
||||
- [ ] tracker_id is valid (1, 2, or 4 most common)
|
||||
- [ ] category_id matches project categories
|
||||
- [ ] TYPO3 version custom field included (id: 4)
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Subject Lines
|
||||
|
||||
✅ **Good**:
|
||||
- "Indexed search crashes on malformed UTF-8"
|
||||
- "Add WebP image format support"
|
||||
- "Standardize commit-msg hook error message"
|
||||
|
||||
❌ **Bad**:
|
||||
- "Fix bug"
|
||||
- "Improvement needed"
|
||||
- "Question about feature"
|
||||
|
||||
### Descriptions
|
||||
|
||||
**Structure**:
|
||||
1. Brief summary (what is the issue)
|
||||
2. Steps to reproduce (for bugs)
|
||||
3. Expected behavior
|
||||
4. Actual behavior
|
||||
5. Additional context
|
||||
|
||||
**Include**:
|
||||
- Error messages
|
||||
- Stack traces
|
||||
- Version information
|
||||
- Configuration details
|
||||
|
||||
**Avoid**:
|
||||
- Asking questions (use Slack instead)
|
||||
- Multiple unrelated issues in one ticket
|
||||
- Vague descriptions without details
|
||||
|
||||
### Categories
|
||||
|
||||
Choose most specific category:
|
||||
- Not "Miscellaneous" if more specific exists
|
||||
- "Backend API" for backend PHP code
|
||||
- "Backend User Interface" for backend UI/UX
|
||||
- "Frontend" for frontend rendering
|
||||
- Component-specific for extensions (e.g., "Indexed Search")
|
||||
|
||||
### Priority
|
||||
|
||||
**Guidelines**:
|
||||
- **Must have (3)**: Blocking issues, critical bugs, security
|
||||
- **Should have (4)**: Normal bugs, important features (most common)
|
||||
- **Could have (5)**: Nice-to-have features, minor improvements
|
||||
- **Nice to have (2)**: Low priority, future considerations
|
||||
|
||||
## Automation Tips
|
||||
|
||||
### Store Issue Template
|
||||
|
||||
```bash
|
||||
cat > /tmp/issue-template.json <<'EOF'
|
||||
{
|
||||
"issue": {
|
||||
"project_id": "typo3cms-core",
|
||||
"subject": "",
|
||||
"description": "",
|
||||
"tracker_id": 1,
|
||||
"category_id": 975,
|
||||
"priority_id": 4,
|
||||
"custom_fields": [
|
||||
{"id": 4, "value": "13"}
|
||||
]
|
||||
}
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
### Parse Response
|
||||
|
||||
```bash
|
||||
# Extract issue number
|
||||
ISSUE_ID=$(curl ... | jq -r '.issue.id')
|
||||
|
||||
# Build URL
|
||||
ISSUE_URL="https://forge.typo3.org/issues/${ISSUE_ID}"
|
||||
|
||||
# Use in commit message
|
||||
echo "Resolves: #${ISSUE_ID}"
|
||||
```
|
||||
|
||||
### Batch Operations
|
||||
|
||||
Query multiple issues:
|
||||
```bash
|
||||
for id in 105737 107881 108000; do
|
||||
curl -H "X-Redmine-API-Key: $FORGE_API_KEY" \
|
||||
"https://forge.typo3.org/issues/${id}.json"
|
||||
done
|
||||
```
|
||||
|
||||
## Integration with Git
|
||||
|
||||
### Create Issue and Commit
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Create issue
|
||||
RESPONSE=$(curl -s -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Redmine-API-Key: $FORGE_API_KEY" \
|
||||
-d @issue.json \
|
||||
https://forge.typo3.org/issues.json)
|
||||
|
||||
# Extract issue number
|
||||
ISSUE_ID=$(echo "$RESPONSE" | jq -r '.issue.id')
|
||||
|
||||
# Use in commit
|
||||
git commit -m "[BUGFIX] Fix the problem
|
||||
|
||||
Resolves: #${ISSUE_ID}
|
||||
Releases: main"
|
||||
```
|
||||
|
||||
### Link Gerrit Patch to Issue
|
||||
|
||||
After submitting to Gerrit:
|
||||
```bash
|
||||
GERRIT_URL="https://review.typo3.org/c/Packages/TYPO3.CMS/+/91302"
|
||||
# Add comment to Forge issue with patch link
|
||||
# (requires additional API call - see Redmine API docs)
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- **Redmine API Docs**: https://www.redmine.org/projects/redmine/wiki/Rest_api
|
||||
- **TYPO3 Forge**: https://forge.typo3.org
|
||||
- **API Access Key**: https://forge.typo3.org/my/account
|
||||
- **Project Info**: https://forge.typo3.org/projects/typo3cms-core
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Task | Endpoint | Method |
|
||||
|------|----------|--------|
|
||||
| Create issue | `/issues.json` | POST |
|
||||
| Get issue | `/issues/{id}.json` | GET |
|
||||
| Update issue | `/issues/{id}.json` | PUT |
|
||||
| Get project | `/projects/{id}.json` | GET |
|
||||
| List issues | `/issues.json?project_id=typo3cms-core` | GET |
|
||||
|
||||
## See Also
|
||||
|
||||
- `scripts/create-forge-issue.sh` - Interactive issue creation
|
||||
- `scripts/query-forge-metadata.sh` - Query project metadata
|
||||
- `references/commit-message-format.md` - For using issue numbers in commits
|
||||
@@ -0,0 +1,386 @@
|
||||
# TYPO3 Gerrit Review Patterns - Real-World Insights
|
||||
|
||||
Based on analysis of actual merged patches from review.typo3.org, this document captures common review patterns, expectations, and best practices.
|
||||
|
||||
## Understanding Revision Counts
|
||||
|
||||
**Key Insight**: High revision counts (7-25 patch sets) are NORMAL and expected, not a sign of failure.
|
||||
|
||||
**Real Examples**:
|
||||
- Change #90226: 24 patch sets (functional tests for Extbase FileUpload)
|
||||
- Change #88519: 14 patch sets (breaking change - Record API)
|
||||
- Change #91161: 9 patch sets (DI refactoring)
|
||||
- Change #91284: 7 patch sets (pagetree performance)
|
||||
|
||||
**What causes multiple revisions**:
|
||||
1. CI failures requiring fixes
|
||||
2. Rebases due to base branch updates
|
||||
3. Code quality refinements based on reviewer feedback
|
||||
4. Architectural improvements suggested by core team
|
||||
5. Edge case handling
|
||||
6. Scope adjustments (e.g., backport constraints)
|
||||
|
||||
**Mindset**: Each revision makes the patch better. Multiple revisions show:
|
||||
- Responsiveness to feedback
|
||||
- Iterative improvement
|
||||
- Collaboration with core team
|
||||
- Thorough vetting process
|
||||
|
||||
## Common Reviewer Feedback Themes
|
||||
|
||||
### 1. Architectural Alignment
|
||||
|
||||
**Pattern**: Leverage framework patterns over custom solutions
|
||||
|
||||
**Example from #91161**:
|
||||
```
|
||||
Reviewer: "Just make this method no-op with only the trigger_error() and
|
||||
remove $this->instances since the service locator will catch classes that
|
||||
implements the interface automatically."
|
||||
```
|
||||
|
||||
**Expectation**:
|
||||
- Use dependency injection (DI) over manual instance management
|
||||
- Leverage service locators for interface-based registration
|
||||
- Follow TYPO3 framework patterns
|
||||
- Avoid reinventing framework capabilities
|
||||
|
||||
**Best Practice**: Before implementing, check if TYPO3 framework already provides the pattern.
|
||||
|
||||
### 2. Configuration Best Practices
|
||||
|
||||
**Pattern**: Services.yaml configuration matters
|
||||
|
||||
**Example from #90226**:
|
||||
```
|
||||
Reviewer: "Why is Domain/Validator/ excluded here? This would prevent
|
||||
validators from receiving dependency injection."
|
||||
```
|
||||
|
||||
**Expectation**:
|
||||
- Understand Services.yaml exclusion patterns
|
||||
- Don't copy boilerplate without understanding
|
||||
- Enable DI for all appropriate classes
|
||||
- Reference official documentation for patterns
|
||||
|
||||
**Best Practice**: Review Services.yaml carefully, don't blindly copy from examples.
|
||||
|
||||
### 3. Performance Validation
|
||||
|
||||
**Pattern**: Performance claims require empirical evidence
|
||||
|
||||
**Example from #91284**:
|
||||
```
|
||||
Reviewer: "It performs much better. If a large number of pages are open,
|
||||
there is still a very slight delay (milliseconds), but this should not
|
||||
cause any problems."
|
||||
|
||||
Reviewer: "Nice one here, removing the expensive calls to this.nodes.find()
|
||||
- reduced from O(n^(m²)) [30ms] to O(n) [2-3ms]"
|
||||
```
|
||||
|
||||
**Expectation**:
|
||||
- Test performance fixes in production-like environments
|
||||
- Provide computational complexity analysis
|
||||
- Measure actual performance improvements
|
||||
- Document before/after metrics
|
||||
- Multiple reviewers test independently
|
||||
|
||||
**Best Practice**: Include performance measurements in commit message or comments.
|
||||
|
||||
### 4. Breaking Changes Documentation
|
||||
|
||||
**Pattern**: Breaking changes need explicit communication
|
||||
|
||||
**Example from #88519**:
|
||||
```
|
||||
Reviewer: "The info that the item.record is now a record object, is
|
||||
important to know for externals."
|
||||
|
||||
Reviewer: "That's breaking IMHO" (regarding API change)
|
||||
```
|
||||
|
||||
**Expectation**:
|
||||
- Document API changes affecting extension developers
|
||||
- Use `[!!!]` prefix for breaking changes
|
||||
- Add deprecations with `trigger_error()` for BC breaks
|
||||
- Consider backport constraints (may limit to main branch)
|
||||
- Provide migration examples
|
||||
|
||||
**Best Practice**: Always think about extension developers when changing public APIs.
|
||||
|
||||
### 5. Code Quality Standards
|
||||
|
||||
**Pattern**: Modern PHP practices and clean code
|
||||
|
||||
**Recurring feedback themes**:
|
||||
- Use named arguments in function calls
|
||||
- Separate concerns (split large functions into classes)
|
||||
- Improve readability through refactoring
|
||||
- Handle edge cases explicitly
|
||||
- Remove unused code
|
||||
|
||||
**Best Practice**: Follow PSR-12 and modern PHP 8+ features.
|
||||
|
||||
### 6. Test Stability Focus
|
||||
|
||||
**Pattern**: Tests serve as API stability monitors
|
||||
|
||||
**Example from #90226**:
|
||||
```
|
||||
Reviewer: "These tests could serve as an important whistleblower with
|
||||
extbase to monitor API stability and how frequently changes are needed."
|
||||
```
|
||||
|
||||
**Expectation**:
|
||||
- Tests should catch unintended API changes
|
||||
- Test scope should be "as simple as possible and as complex as needed"
|
||||
- Functional tests preferred over unit tests for integration points
|
||||
- Tests validate real-world usage patterns
|
||||
|
||||
**Best Practice**: Write tests that detect breaking changes, not just code coverage.
|
||||
|
||||
### 7. Iterative Refinement Philosophy
|
||||
|
||||
**Pattern**: Patches improve through collaboration, not rejection
|
||||
|
||||
**Observed patterns**:
|
||||
- Positive language: "I like! 🙌", "Awesome job!", "Nice one here"
|
||||
- Constructive suggestions: "You could...", "Consider...", "What about..."
|
||||
- Collaborative problem-solving: Multiple reviewers contribute ideas
|
||||
- Incremental improvements: Each revision refines the approach
|
||||
|
||||
**Expectation**:
|
||||
- Be responsive to feedback
|
||||
- Implement suggested improvements
|
||||
- Ask clarifying questions when needed
|
||||
- Iterate toward excellence
|
||||
|
||||
**Best Practice**: View reviews as mentoring, not gatekeeping.
|
||||
|
||||
## Common Revision Patterns
|
||||
|
||||
### Pattern 1: CI Failure Cycle
|
||||
|
||||
**Typical flow**:
|
||||
1. Initial submission
|
||||
2. CI rejects (CGL, PHPStan, tests)
|
||||
3. Fix CI issues
|
||||
4. Resubmit
|
||||
5. New CI issues found
|
||||
6. Repeat until green
|
||||
|
||||
**Prevention**: Use typo3-conformance-skill and typo3-testing-skill BEFORE first submission.
|
||||
|
||||
### Pattern 2: Rebase Cycle
|
||||
|
||||
**Typical flow**:
|
||||
1. Patch submitted
|
||||
2. Base branch updated with other changes
|
||||
3. Gerrit shows "needs rebase"
|
||||
4. Rebase on latest main
|
||||
5. Resolve conflicts
|
||||
6. CI runs again (may reveal new issues)
|
||||
7. Repeat as needed
|
||||
|
||||
**Prevention**: Rebase regularly during development, not just at submission.
|
||||
|
||||
### Pattern 3: Scope Adjustment
|
||||
|
||||
**Typical flow**:
|
||||
1. Patch targets main + version branches
|
||||
2. Review reveals backport complexity
|
||||
3. Dependencies on other changes discovered
|
||||
4. Scope changed to "main only"
|
||||
5. Commit message updated
|
||||
|
||||
**Prevention**: Check dependencies before claiming backport compatibility.
|
||||
|
||||
### Pattern 4: Architecture Refinement
|
||||
|
||||
**Typical flow**:
|
||||
1. Working implementation submitted
|
||||
2. Reviewer suggests better framework pattern
|
||||
3. Refactor to use framework capabilities
|
||||
4. Simplify code by removing custom logic
|
||||
5. May take 3-5 revisions to align
|
||||
|
||||
**Prevention**: Study framework patterns before implementing custom solutions.
|
||||
|
||||
## Review Timeline Expectations
|
||||
|
||||
Based on analyzed patches:
|
||||
|
||||
**Simple changes** (1-3 files, no breaking changes):
|
||||
- Review starts: Within 1-2 days
|
||||
- First feedback: 2-3 days
|
||||
- Typical revisions: 2-5 patch sets
|
||||
- Merge time: 1-2 weeks
|
||||
|
||||
**Complex changes** (multiple files, new features):
|
||||
- Review starts: Within 3-5 days
|
||||
- First feedback: 3-7 days
|
||||
- Typical revisions: 7-15 patch sets
|
||||
- Merge time: 2-4 weeks
|
||||
|
||||
**Breaking changes** (API changes, [!!!]):
|
||||
- Review starts: Within 1-2 days
|
||||
- First feedback: 1-3 days (architectural concerns raised early)
|
||||
- Typical revisions: 10-20 patch sets
|
||||
- Merge time: 3-6 weeks (due to documentation, deprecation)
|
||||
|
||||
**Performance fixes**:
|
||||
- Review starts: Within 1-2 days
|
||||
- Testing phase: 1-2 weeks (reviewers test in production)
|
||||
- Typical revisions: 5-10 patch sets
|
||||
- Merge time: 2-3 weeks
|
||||
|
||||
## Key Reviewers and Their Focus Areas
|
||||
|
||||
Based on observed patterns:
|
||||
|
||||
**Christian Kuhn (lolli)**:
|
||||
- Architectural alignment
|
||||
- Framework pattern usage
|
||||
- Test quality and coverage
|
||||
- Long-term maintainability
|
||||
|
||||
**Benni Mack**:
|
||||
- Breaking change implications
|
||||
- Extension developer impact
|
||||
- API design
|
||||
- Documentation completeness
|
||||
|
||||
**Stefan Bürk**:
|
||||
- Configuration best practices
|
||||
- Services.yaml patterns
|
||||
- Dependency injection
|
||||
- Code quality standards
|
||||
|
||||
**Pattern**: Different reviewers have different expertise areas. Address each reviewer's specific concerns.
|
||||
|
||||
## Best Practices from Real Reviews
|
||||
|
||||
### Do's
|
||||
|
||||
✅ **Respond to every comment**: Even if just "Done" or "Fixed in PS X"
|
||||
✅ **Test in production-like environments**: Especially for performance fixes
|
||||
✅ **Use framework patterns**: DI, service locators, event dispatchers
|
||||
✅ **Document breaking changes**: Think about extension developers
|
||||
✅ **Iterate based on feedback**: Don't defend, improve
|
||||
✅ **Keep scope focused**: Don't expand scope during review
|
||||
✅ **Update commit messages**: Reflect scope or approach changes
|
||||
✅ **Add deprecations properly**: Use trigger_error() for BC breaks
|
||||
|
||||
### Don'ts
|
||||
|
||||
❌ **Don't take high revision counts personally**: They're normal and expected
|
||||
❌ **Don't copy boilerplate blindly**: Understand configuration patterns
|
||||
❌ **Don't skip testing**: CI will catch it anyway
|
||||
❌ **Don't ignore architectural feedback**: Core team guides for good reasons
|
||||
❌ **Don't rush rebases**: Test after rebasing
|
||||
❌ **Don't claim performance without metrics**: Provide evidence
|
||||
❌ **Don't break APIs without [!!!]**: Use proper prefixes
|
||||
❌ **Don't argue with multiple reviewers**: If 2+ reviewers agree, they're probably right
|
||||
|
||||
## Handling Common Situations
|
||||
|
||||
### Situation 1: "My patch has 10 revisions already"
|
||||
|
||||
**Response**: This is normal! Changes #90226 had 24, #88519 had 14. Keep iterating.
|
||||
|
||||
**Action**:
|
||||
1. Review all outstanding comments
|
||||
2. Address each systematically
|
||||
3. Test thoroughly after each change
|
||||
4. Mark comments as resolved with explanation
|
||||
5. Keep positive attitude
|
||||
|
||||
### Situation 2: "Reviewer suggested complete refactoring"
|
||||
|
||||
**Response**: Core team is guiding toward better patterns. This is mentoring.
|
||||
|
||||
**Action**:
|
||||
1. Ask clarifying questions if needed
|
||||
2. Study the suggested pattern
|
||||
3. Implement as suggested
|
||||
4. Don't defend original approach
|
||||
5. Learn framework patterns for future
|
||||
|
||||
### Situation 3: "CI keeps failing after fixes"
|
||||
|
||||
**Response**: Each rebase can reveal new issues. This is expected.
|
||||
|
||||
**Action**:
|
||||
1. Use typo3-conformance-skill locally
|
||||
2. Use typo3-testing-skill for test failures
|
||||
3. Validate BEFORE pushing
|
||||
4. Consider environment differences
|
||||
5. Ask for help if stuck
|
||||
|
||||
### Situation 4: "Scope changed from 'main + 13.4' to 'main only'"
|
||||
|
||||
**Response**: Backport complexity discovered during review. Common pattern.
|
||||
|
||||
**Action**:
|
||||
1. Update commit message (Releases: main)
|
||||
2. Update Forge issue target version
|
||||
3. Don't argue - backporting is complex
|
||||
4. Focus on getting main merged first
|
||||
5. Backport can be separate patch later
|
||||
|
||||
## Learning from Reviews
|
||||
|
||||
### What to Extract from Reviews
|
||||
|
||||
When reading other reviews:
|
||||
1. **Architectural patterns**: How do they structure code?
|
||||
2. **Framework usage**: What TYPO3 APIs do they leverage?
|
||||
3. **Testing approaches**: How do they test complex scenarios?
|
||||
4. **Documentation style**: How do they explain breaking changes?
|
||||
5. **Reviewer priorities**: What concerns get raised most?
|
||||
|
||||
### How to Improve
|
||||
|
||||
Based on review patterns:
|
||||
1. **Study merged patches**: See what passes review
|
||||
2. **Read reviewer comments**: Learn what matters to core team
|
||||
3. **Use framework patterns**: Follow existing approaches
|
||||
4. **Test thoroughly**: Validate locally before submission
|
||||
5. **Be responsive**: Quick turnaround on feedback
|
||||
6. **Stay positive**: Reviews are mentoring, not rejection
|
||||
|
||||
## Summary: Review Success Pattern
|
||||
|
||||
**Before submission**:
|
||||
- ✅ Use typo3-conformance-skill
|
||||
- ✅ Use typo3-testing-skill
|
||||
- ✅ Study framework patterns
|
||||
- ✅ Check Services.yaml configuration
|
||||
- ✅ Test in realistic environment
|
||||
|
||||
**During review**:
|
||||
- ✅ Respond to all comments promptly
|
||||
- ✅ Implement suggestions positively
|
||||
- ✅ Test after each revision
|
||||
- ✅ Update commit message as needed
|
||||
- ✅ Ask questions when unclear
|
||||
|
||||
**Mindset**:
|
||||
- ✅ Multiple revisions are normal and healthy
|
||||
- ✅ Reviews improve your code
|
||||
- ✅ Core team is mentoring you
|
||||
- ✅ Each iteration makes TYPO3 better
|
||||
- ✅ You're learning framework patterns
|
||||
|
||||
## References
|
||||
|
||||
**Analyzed patches**:
|
||||
- #90226: Extbase FileUpload functional tests (24 PS)
|
||||
- #91161: DI in ExtractorService (9 PS)
|
||||
- #91284: Pagetree performance (7 PS)
|
||||
- #88519: Record API breaking change (14 PS)
|
||||
|
||||
**Review platform**: https://review.typo3.org
|
||||
|
||||
**Remember**: The best contributors don't have the fewest revisions - they have the most responsive and collaborative review interactions.
|
||||
783
skills/typo3-core-contributions/references/gerrit-workflow.md
Normal file
783
skills/typo3-core-contributions/references/gerrit-workflow.md
Normal file
@@ -0,0 +1,783 @@
|
||||
# TYPO3 Gerrit Workflow Guide
|
||||
|
||||
Comprehensive guide for working with Gerrit code review system in TYPO3 Core contributions.
|
||||
|
||||
## What is Gerrit?
|
||||
|
||||
Gerrit is a web-based code review tool that TYPO3 uses for managing patch submissions. Every code change must go through Gerrit review before being merged into TYPO3 Core.
|
||||
|
||||
**Key Concepts**:
|
||||
- **Patch/Change**: Single commit representing your contribution
|
||||
- **Patchset**: Version of a patch (same Change-Id, updated code)
|
||||
- **Review**: Process of evaluating code quality and correctness
|
||||
- **Merge**: Final acceptance of patch into TYPO3 Core
|
||||
|
||||
## Accessing Gerrit
|
||||
|
||||
**URL**: https://review.typo3.org
|
||||
|
||||
**Authentication**: Use TYPO3.org account credentials
|
||||
|
||||
**Search Tool**: https://forger.typo3.com (easier searching)
|
||||
|
||||
## Submitting Your First Patch
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Git configured for TYPO3 (see Environment Setup)
|
||||
- SSH keys added to Gerrit
|
||||
- Commit ready with proper message format
|
||||
- All changes in single commit
|
||||
|
||||
### Push to Gerrit
|
||||
|
||||
```bash
|
||||
# From your feature branch
|
||||
git push origin HEAD:refs/for/main
|
||||
```
|
||||
|
||||
**What happens**:
|
||||
1. Git pushes to special Gerrit ref: `refs/for/main`
|
||||
2. Gerrit creates new review
|
||||
3. You receive SUCCESS message with review URL
|
||||
|
||||
**Expected Output**:
|
||||
```
|
||||
remote: Processing changes: new: 1, done
|
||||
remote:
|
||||
remote: SUCCESS
|
||||
remote:
|
||||
remote: https://review.typo3.org/c/Packages/TYPO3.CMS/+/12345 [NEW]
|
||||
remote:
|
||||
To ssh://review.typo3.org:29418/Packages/TYPO3.CMS.git
|
||||
* [new branch] HEAD -> refs/for/main
|
||||
```
|
||||
|
||||
**Save the review URL!** You'll need it to monitor progress.
|
||||
|
||||
### Alternative Push Methods
|
||||
|
||||
If you've configured default push settings:
|
||||
|
||||
```bash
|
||||
# Simple push (if remote.origin.push configured)
|
||||
git push
|
||||
```
|
||||
|
||||
## Continuous Integration
|
||||
|
||||
After submission, Gerrit automatically runs tests:
|
||||
|
||||
1. **GitLab CI** triggers test pipeline
|
||||
2. Tests run across multiple PHP versions, code style checks, static analysis (PHPStan), and unit/functional tests
|
||||
3. Results appear on Gerrit review page
|
||||
4. Usually completes in 10-20 minutes
|
||||
|
||||
**Status Indicators**:
|
||||
- ✅ Green checkmark: All tests passed
|
||||
- ❌ Red X: Tests failed
|
||||
- ⏳ Clock: Tests running
|
||||
|
||||
### IMPORTANT: New Patches Start in "Work in Progress" State
|
||||
|
||||
**By default, newly submitted patches are marked as WIP (Work in Progress)**. This means:
|
||||
|
||||
1. ⚠️ **Not visible to reviewers** - Core team won't see your patch for review
|
||||
2. ✅ **CI tests still run** - You get test feedback immediately
|
||||
3. 🔍 **You must verify yourself first** - Check all CI jobs before requesting review
|
||||
4. ✅ **You must manually mark as ready** - Change state to "Ready for Review" when done
|
||||
|
||||
**Workflow for New Submissions**:
|
||||
|
||||
```bash
|
||||
# 1. Push your patch
|
||||
git push origin HEAD:refs/for/main
|
||||
|
||||
# 2. Note the review URL from output
|
||||
# https://review.typo3.org/c/Packages/TYPO3.CMS/+/12345 [WIP]
|
||||
|
||||
# 3. Wait for CI to complete (10-20 minutes)
|
||||
|
||||
# 4. CHECK ALL FAILING JOBS (critical step!)
|
||||
# - Open the review URL
|
||||
# - Look for any CI failures (red X marks)
|
||||
# - For each failure, find the GitLab job URL
|
||||
# - Read the ACTUAL ERROR LOGS (don't guess!)
|
||||
# - Fix ALL issues before marking ready
|
||||
|
||||
# 5. Once all tests pass, mark as ready for review
|
||||
#
|
||||
# Option A: Remove WIP via command line (empty push)
|
||||
git commit --amend --allow-empty --no-edit
|
||||
git push origin HEAD:refs/for/main%ready
|
||||
|
||||
# Option B: Remove WIP via web UI
|
||||
# a. Open review URL: https://review.typo3.org/c/Packages/TYPO3.CMS/+/XXXXX
|
||||
# b. Click "Start Review" button (top right area, near your avatar)
|
||||
#
|
||||
# Notes:
|
||||
# - %ready flag removes WIP state (even with empty pushes)
|
||||
# - %wip flag sets WIP state: git push origin HEAD:refs/for/main%wip
|
||||
# - SSH 'gerrit review' command does NOT support WIP flags (use git push flags instead)
|
||||
```
|
||||
|
||||
### Investigating CI Failures (CRITICAL!)
|
||||
|
||||
**NEVER assume what failed - ALWAYS check the actual job logs!**
|
||||
|
||||
#### Step 1: Find All Failing Jobs
|
||||
|
||||
On your Gerrit review page:
|
||||
1. Scroll to the CI results section
|
||||
2. Look for red ❌ marks next to job names
|
||||
3. Note ALL failing job names (there might be multiple!)
|
||||
|
||||
Common failing jobs:
|
||||
- `cgl pre-merge` - Code style violations (PHP CS Fixer)
|
||||
- `phpstan php X.X pre-merge` - Static analysis errors
|
||||
- `unit php X.X pre-merge` - Unit test failures
|
||||
- `functional php X.X pre-merge` - Functional test failures
|
||||
|
||||
#### Step 2: Access GitLab Job Logs
|
||||
|
||||
For each failing job:
|
||||
1. Click on the failing job name in Gerrit
|
||||
2. You'll be redirected to GitLab CI (https://git.typo3.org/typo3/CI/cms/-/jobs/XXXXXX)
|
||||
3. Click the job log or raw log to see the actual error
|
||||
|
||||
**Example**: If job #4896429 failed:
|
||||
- URL: `https://git.typo3.org/typo3/CI/cms/-/jobs/4896429`
|
||||
- Raw log: `https://git.typo3.org/typo3/CI/cms/-/jobs/4896429/raw`
|
||||
|
||||
#### Step 3: Read and Understand ACTUAL Errors
|
||||
|
||||
**DO NOT GUESS!** Read the actual error messages:
|
||||
|
||||
**Code Style (cgl) Example**:
|
||||
```
|
||||
Fixed 1 of 1 files in ... seconds.
|
||||
|
||||
Checked 1 of 1 files in ... seconds.
|
||||
1) typo3/sysext/indexed_search/Tests/Unit/IndexerTest.php (single_quote)
|
||||
---------- begin diff ----------
|
||||
- body: "This content should not appear"
|
||||
+ body: 'This content should not appear'
|
||||
----------- end diff -----------
|
||||
```
|
||||
**Fix**: Change double quotes to single quotes in test file.
|
||||
|
||||
**PHPStan Example**:
|
||||
```
|
||||
------ -------------------------------------------------------------------------
|
||||
Line indexed_search/Tests/Unit/IndexerTest.php
|
||||
------ -------------------------------------------------------------------------
|
||||
236 Call to static method PHPUnit\Framework\Assert::assertNotNull()
|
||||
with string will always evaluate to true.
|
||||
------ -------------------------------------------------------------------------
|
||||
```
|
||||
**Fix**: Remove `assertNotNull()` call - it's redundant for string return types.
|
||||
|
||||
**Unit Test Failure Example**:
|
||||
```
|
||||
FAILURES!
|
||||
Tests: 11683, Assertions: 20300, Failures: 1.
|
||||
|
||||
There was 1 failure:
|
||||
|
||||
1) TYPO3\CMS\IndexedSearch\Tests\Unit\IndexerTest::bodyDescriptionReturnsEmptyStringWhenMaxLengthIsZero
|
||||
Failed asserting that two strings are equal.
|
||||
--- Expected
|
||||
+++ Actual
|
||||
@@ @@
|
||||
-''
|
||||
+'This content should not appear in description'
|
||||
```
|
||||
**Fix**: Test logic is wrong - review the test expectations.
|
||||
|
||||
#### Step 4: Fix ALL Issues
|
||||
|
||||
⚠️ **CRITICAL**: A CI pipeline may have multiple failing jobs. Fix ALL of them:
|
||||
|
||||
```bash
|
||||
# Example: 5 jobs failed (cgl, phpstan, 3x unit tests)
|
||||
# You must fix:
|
||||
# 1. Code style issues (single quotes)
|
||||
# 2. PHPStan warnings (remove redundant assertions)
|
||||
# 3. Unit test failures (fix test logic)
|
||||
|
||||
# Make all fixes
|
||||
vim typo3/sysext/indexed_search/Tests/Unit/IndexerTest.php
|
||||
|
||||
# Stage changes
|
||||
git add typo3/sysext/indexed_search/Tests/Unit/IndexerTest.php
|
||||
|
||||
# Amend commit
|
||||
git commit --amend --no-edit
|
||||
|
||||
# Push updated patchset
|
||||
git push origin HEAD:refs/for/main
|
||||
```
|
||||
|
||||
#### Step 5: Wait for Re-verification
|
||||
|
||||
After pushing fixes:
|
||||
1. CI automatically runs again
|
||||
2. Old failed votes (Verified-1) are removed
|
||||
3. Wait for all jobs to complete
|
||||
4. Verify ALL jobs are now passing (green ✅)
|
||||
|
||||
#### Step 6: Mark as Ready for Review
|
||||
|
||||
Once ALL CI jobs pass:
|
||||
1. Open your review on Gerrit
|
||||
2. Click **"More"** → **"Start Review"**
|
||||
3. Optionally add a comment: "Ready for review. All CI checks passing."
|
||||
4. Your patch is now visible to core team reviewers
|
||||
|
||||
### Common CI Failure Patterns
|
||||
|
||||
| Job Type | Common Issues | Where to Look |
|
||||
|----------|---------------|---------------|
|
||||
| cgl (Code Style) | Double quotes, spacing, indentation | PHP CS Fixer diff in log |
|
||||
| phpstan | Type errors, redundant code, undefined vars | Line numbers + error descriptions |
|
||||
| unit tests | Test failures, assertion mismatches | Test name + expected vs actual |
|
||||
| functional tests | Database issues, integration problems | Full stack trace in log |
|
||||
|
||||
**If tests fail**:
|
||||
1. ⚠️ **DO NOT GUESS** - Always read actual job logs
|
||||
2. Check ALL failing jobs, not just the first one
|
||||
3. Access GitLab CI job logs via links on Gerrit
|
||||
4. Fix all issues in one patchset
|
||||
5. Push updated patchset (next section)
|
||||
6. Wait for re-verification
|
||||
7. Mark as ready only when ALL jobs pass
|
||||
|
||||
## Updating Your Patch
|
||||
|
||||
When reviewers request changes or tests fail:
|
||||
|
||||
### Step 1: Make Changes Locally
|
||||
|
||||
```bash
|
||||
# Make code changes
|
||||
vim path/to/file.php
|
||||
|
||||
# Stage changes
|
||||
git add path/to/file.php
|
||||
```
|
||||
|
||||
### Step 2: Amend Commit
|
||||
|
||||
```bash
|
||||
# Amend existing commit (DO NOT create new commit!)
|
||||
git commit --amend
|
||||
|
||||
# CRITICAL: Keep the Change-Id line unchanged!
|
||||
```
|
||||
|
||||
### Step 3: Push Updated Patchset
|
||||
|
||||
```bash
|
||||
# Push to same Gerrit change
|
||||
git push origin HEAD:refs/for/main
|
||||
```
|
||||
|
||||
**What happens**:
|
||||
- Gerrit matches Change-Id
|
||||
- Creates new patchset (Patch Set 2, 3, etc.)
|
||||
- Previous patchsets remain for comparison
|
||||
- CI tests run again
|
||||
|
||||
**Patchset Versioning**:
|
||||
- Patch Set 1: Initial submission
|
||||
- Patch Set 2: First update
|
||||
- Patch Set 3: Second update
|
||||
- etc.
|
||||
|
||||
## Rebasing Your Patch
|
||||
|
||||
### Why Rebase?
|
||||
|
||||
While you're working, other contributors' patches get merged. Your patch becomes based on outdated code. Rebasing updates your patch to build on the latest codebase.
|
||||
|
||||
### When to Rebase
|
||||
|
||||
- Merge conflict indicator appears on Gerrit
|
||||
- Regularly during development (best practice)
|
||||
- Before running tests
|
||||
- When requested by reviewers
|
||||
|
||||
### Method 1: Browser-Based Rebase (Easiest)
|
||||
|
||||
**Requirements**: No merge conflicts
|
||||
|
||||
**Steps**:
|
||||
1. Open your patch on Gerrit
|
||||
2. Click **Rebase** button (top right)
|
||||
3. Select "Rebase on top of the main branch"
|
||||
4. Click **Rebase**
|
||||
|
||||
Gerrit automatically:
|
||||
- Rebases your change
|
||||
- Creates new patchset
|
||||
- Runs CI tests
|
||||
|
||||
### Method 2: Command-Line Rebase
|
||||
|
||||
**When to use**: Merge conflicts exist, or prefer manual control
|
||||
|
||||
**Steps**:
|
||||
|
||||
```bash
|
||||
# Ensure on your feature branch
|
||||
git checkout feature/105737-fix-indexed-search
|
||||
|
||||
# Fetch latest changes
|
||||
git fetch origin
|
||||
|
||||
# Rebase onto main
|
||||
git rebase origin/main
|
||||
```
|
||||
|
||||
**If no conflicts**:
|
||||
```bash
|
||||
# Push rebased patch
|
||||
git push origin HEAD:refs/for/main
|
||||
```
|
||||
|
||||
**If conflicts occur**: See Resolving Merge Conflicts section below.
|
||||
|
||||
### Alternative Rebase Methods
|
||||
|
||||
**Option A: Pull with Rebase**
|
||||
```bash
|
||||
git pull --rebase origin main
|
||||
```
|
||||
|
||||
**Option B: Interactive Rebase** (advanced)
|
||||
```bash
|
||||
git rebase -i origin/main
|
||||
```
|
||||
|
||||
## Resolving Merge Conflicts
|
||||
|
||||
### What Are Conflicts?
|
||||
|
||||
Conflicts occur when:
|
||||
- You modified file X
|
||||
- Someone else modified same lines in file X
|
||||
- Their patch merged first
|
||||
- Git can't auto-merge
|
||||
|
||||
### Conflict Resolution Process
|
||||
|
||||
#### Step 1: Start Rebase
|
||||
|
||||
```bash
|
||||
git rebase origin/main
|
||||
```
|
||||
|
||||
**Output with conflicts**:
|
||||
```
|
||||
CONFLICT (content): Merge conflict in path/to/file.php
|
||||
error: could not apply abc123... Your commit message
|
||||
hint: Resolve all conflicts manually, mark them as resolved with
|
||||
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
|
||||
```
|
||||
|
||||
#### Step 2: Identify Conflicted Files
|
||||
|
||||
```bash
|
||||
git status
|
||||
```
|
||||
|
||||
**Output**:
|
||||
```
|
||||
On branch feature/105737-fix-indexed-search
|
||||
You are currently rebasing branch 'feature/105737-fix-indexed-search' on 'abc123'.
|
||||
(fix conflicts and then run "git rebase --continue")
|
||||
|
||||
Unmerged paths:
|
||||
(use "git add <file>..." to mark resolution)
|
||||
both modified: path/to/file.php
|
||||
```
|
||||
|
||||
#### Step 3: Resolve Conflicts
|
||||
|
||||
Open conflicted file in editor:
|
||||
|
||||
```php
|
||||
<<<<<<< HEAD
|
||||
// Code from main branch (their changes)
|
||||
$result = newFunction($data);
|
||||
=======
|
||||
// Your changes
|
||||
$result = oldFunction($data);
|
||||
>>>>>>> Your commit message
|
||||
```
|
||||
|
||||
**Choose resolution**:
|
||||
|
||||
**Option A: Keep their changes**
|
||||
```php
|
||||
$result = newFunction($data);
|
||||
```
|
||||
|
||||
**Option B: Keep your changes**
|
||||
```php
|
||||
$result = oldFunction($data);
|
||||
```
|
||||
|
||||
**Option C: Merge both** (most common)
|
||||
```php
|
||||
// Updated to use new function while preserving your logic
|
||||
$result = newFunction($processedData);
|
||||
```
|
||||
|
||||
Remove conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`).
|
||||
|
||||
#### Step 4: Mark as Resolved
|
||||
|
||||
```bash
|
||||
# Stage resolved files
|
||||
git add path/to/file.php
|
||||
|
||||
# Check all conflicts resolved
|
||||
git status
|
||||
```
|
||||
|
||||
#### Step 5: Continue Rebase
|
||||
|
||||
```bash
|
||||
git rebase --continue
|
||||
```
|
||||
|
||||
If more conflicts exist, repeat steps 3-5.
|
||||
|
||||
#### Step 6: Push Rebased Patch
|
||||
|
||||
```bash
|
||||
git push origin HEAD:refs/for/main
|
||||
```
|
||||
|
||||
### Conflict Resolution Tips
|
||||
|
||||
**Understand Context**:
|
||||
- Review their changes: `git show HEAD:path/to/file.php`
|
||||
- Review your changes: `git show feature/105737-fix-indexed-search:path/to/file.php`
|
||||
- Check file history: `git log -- path/to/file.php`
|
||||
|
||||
**Test After Resolution**:
|
||||
```bash
|
||||
# Run tests locally
|
||||
composer test:unit
|
||||
composer test:functional
|
||||
|
||||
# Check syntax
|
||||
php -l path/to/file.php
|
||||
```
|
||||
|
||||
**Ask for Help**:
|
||||
- Post in #typo3-cms-coredev Slack
|
||||
- Comment on Gerrit review
|
||||
- Reference conflicting patch if known
|
||||
|
||||
### Aborting Rebase
|
||||
|
||||
If rebase goes wrong:
|
||||
|
||||
```bash
|
||||
git rebase --abort
|
||||
```
|
||||
|
||||
Returns to pre-rebase state. You can try again.
|
||||
|
||||
## Review Process
|
||||
|
||||
### Voting System
|
||||
|
||||
**Code Review** (CR):
|
||||
- **+2**: Looks good, approved
|
||||
- **+1**: Looks mostly good
|
||||
- **0**: Neutral (default)
|
||||
- **-1**: Needs improvement
|
||||
- **-2**: Do not merge (veto)
|
||||
|
||||
**Verified** (V):
|
||||
- **+1**: Tests passed
|
||||
- **0**: Not yet tested
|
||||
- **-1**: Tests failed
|
||||
|
||||
**Merge Requirements**:
|
||||
- At least **+2 Code Review** from core team member
|
||||
- At least **+1 Verified** (CI tests passed)
|
||||
- No unresolved **-2** votes
|
||||
- At least 2 reviewers involved (one must be core team)
|
||||
|
||||
### Typical Review Timeline
|
||||
|
||||
**Simple Bugfixes**: 1-3 days
|
||||
**Medium Features**: 3-7 days
|
||||
**Complex Features**: 1-2 weeks
|
||||
**Breaking Changes**: 2-4 weeks (more scrutiny)
|
||||
|
||||
**Factors affecting timeline**:
|
||||
- Code quality and completeness
|
||||
- Test coverage
|
||||
- Documentation
|
||||
- Reviewer availability (volunteers!)
|
||||
- Complexity and impact
|
||||
|
||||
### Responding to Review Comments
|
||||
|
||||
#### Step 1: Read Feedback Carefully
|
||||
|
||||
- Understand what's being requested
|
||||
- Ask questions if unclear
|
||||
- Check if feedback applies to multiple locations
|
||||
|
||||
#### Step 2: Implement Changes
|
||||
|
||||
```bash
|
||||
# Make requested changes
|
||||
vim path/to/file.php
|
||||
|
||||
# Stage and amend
|
||||
git add path/to/file.php
|
||||
git commit --amend
|
||||
|
||||
# Push update
|
||||
git push origin HEAD:refs/for/main
|
||||
```
|
||||
|
||||
#### Step 3: Respond on Gerrit
|
||||
|
||||
- Click **Reply** button
|
||||
- Address each comment:
|
||||
- "Done" - Simple confirmation
|
||||
- "Fixed in PS3" - Reference patchset number
|
||||
- Explain your approach if different from suggestion
|
||||
- Thank reviewers
|
||||
- Click **Send**
|
||||
|
||||
#### Example Response
|
||||
|
||||
```
|
||||
Thanks for the review!
|
||||
|
||||
> Line 45: Consider using dependency injection
|
||||
|
||||
Good point! I've refactored to use DI in PS3.
|
||||
|
||||
> Line 120: Add type hint
|
||||
|
||||
Added in PS3. Also added return type hints throughout.
|
||||
|
||||
> Missing tests for edge case
|
||||
|
||||
Added test case for empty string input in Tests/Unit/IndexerTest.php
|
||||
```
|
||||
|
||||
### Getting More Reviews
|
||||
|
||||
**If no reviews after 3-4 days**:
|
||||
|
||||
1. **Advertise on Slack** (#typo3-cms-coredev):
|
||||
```
|
||||
I've submitted a patch for #105737 (indexed search crash).
|
||||
Would appreciate reviews when you have time: https://review.typo3.org/c/Packages/TYPO3.CMS/+/12345
|
||||
```
|
||||
|
||||
2. **Check patch quality**:
|
||||
- Tests passing?
|
||||
- Documentation complete?
|
||||
- Follows coding standards?
|
||||
- Clear commit message?
|
||||
|
||||
3. **Ask specific reviewers** (if appropriate):
|
||||
- Maintainers of affected area
|
||||
- Previous contributors to same files
|
||||
- Don't spam or DM randomly!
|
||||
|
||||
## Gerrit Interface Guide
|
||||
|
||||
### Review Page Sections
|
||||
|
||||
**Header**:
|
||||
- Status (Active, Merged, Abandoned)
|
||||
- Subject and description
|
||||
- Owner and reviewers
|
||||
- CI test results
|
||||
|
||||
**Files**:
|
||||
- List of changed files
|
||||
- Click file to see diff
|
||||
- Add inline comments
|
||||
|
||||
**History**:
|
||||
- Patchset versions
|
||||
- Comments and votes
|
||||
- CI results per patchset
|
||||
|
||||
**Related Changes**:
|
||||
- Depends on / Needed by
|
||||
- Related topics
|
||||
- Conflicts with
|
||||
|
||||
### Useful Gerrit Features
|
||||
|
||||
**Diff Views**:
|
||||
- **Side-by-side**: Compare old/new code
|
||||
- **Unified**: Traditional diff format
|
||||
- **Between patchsets**: Compare PS1 vs PS2
|
||||
|
||||
**Search**:
|
||||
- Find your changes: `owner:self status:open`
|
||||
- Find by issue: `bug:105737`
|
||||
- Find by topic: `topic:indexed-search`
|
||||
|
||||
**Keyboard Shortcuts**:
|
||||
- `?`: Show all shortcuts
|
||||
- `u`: Go up to dashboard
|
||||
- `a`: Expand all inline comments
|
||||
- `c`: Compose review comment
|
||||
- `n/p`: Next/previous file
|
||||
|
||||
## Advanced Topics
|
||||
|
||||
### Cherry-Picking Patches
|
||||
|
||||
Apply someone else's patch locally:
|
||||
|
||||
```bash
|
||||
# From Gerrit download dropdown, copy cherry-pick command
|
||||
git fetch origin refs/changes/45/12345/3 && git cherry-pick FETCH_HEAD
|
||||
```
|
||||
|
||||
### Topics
|
||||
|
||||
Group related changes:
|
||||
|
||||
```bash
|
||||
git push origin HEAD:refs/for/main%topic=indexed-search-improvements
|
||||
```
|
||||
|
||||
### Work In Progress (WIP)
|
||||
|
||||
Mark patch as work-in-progress:
|
||||
|
||||
```bash
|
||||
git push origin HEAD:refs/for/main%wip
|
||||
```
|
||||
|
||||
Or on Gerrit web UI: **More** → **Mark as Work In Progress**
|
||||
|
||||
**Use WIP when**:
|
||||
- Patch incomplete, not ready for review
|
||||
- Want CI test results before review
|
||||
- Demonstrating proof of concept
|
||||
|
||||
**Remove WIP**: **More** → **Start Review**
|
||||
|
||||
### Private Changes
|
||||
|
||||
Keep change private (visible only to you and explicit reviewers):
|
||||
|
||||
```bash
|
||||
git push origin HEAD:refs/for/main%private
|
||||
```
|
||||
|
||||
### Draft Comments
|
||||
|
||||
Save review comments without publishing:
|
||||
1. Add comments on files
|
||||
2. Click **Save** instead of **Send**
|
||||
3. Edit later
|
||||
4. Publish when ready
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Change-Id not found"
|
||||
|
||||
**Problem**: Missing or modified Change-Id
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# Ensure commit-msg hook installed
|
||||
ls -la .git/hooks/commit-msg
|
||||
|
||||
# If missing, install
|
||||
composer gerrit:setup
|
||||
|
||||
# Amend commit to generate Change-Id
|
||||
git commit --amend --no-edit
|
||||
```
|
||||
|
||||
### "Prohibited by Gerrit"
|
||||
|
||||
**Problem**: Pushing to wrong branch or permissions issue
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# Verify push URL
|
||||
git config remote.origin.pushurl
|
||||
# Should be: ssh://<username>@review.typo3.org:29418/Packages/TYPO3.CMS.git
|
||||
|
||||
# Push to refs/for/main, not main directly
|
||||
git push origin HEAD:refs/for/main
|
||||
```
|
||||
|
||||
### "No New Changes"
|
||||
|
||||
**Problem**: Pushing identical commit
|
||||
|
||||
**Solution**:
|
||||
- Make actual code changes
|
||||
- Or amend commit message
|
||||
- Then push again
|
||||
|
||||
### Multiple Commits on Branch
|
||||
|
||||
**Problem**: Accidentally created multiple commits
|
||||
|
||||
**Solution**: Squash into one commit
|
||||
```bash
|
||||
# Interactive rebase
|
||||
git rebase -i origin/main
|
||||
|
||||
# In editor, change all but first "pick" to "squash"
|
||||
# Save and exit
|
||||
# Edit combined commit message
|
||||
# Push
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **One commit per patch**: Squash multiple commits into one
|
||||
2. **Rebase regularly**: Stay up-to-date with main branch
|
||||
3. **Preserve Change-Id**: Never modify when amending
|
||||
4. **Respond promptly**: Reply to reviews within 2-3 days
|
||||
5. **Test locally first**: Run tests before pushing
|
||||
6. **Clear communication**: Explain changes in Gerrit comments
|
||||
7. **Be patient**: Reviewers are volunteers
|
||||
8. **Learn from feedback**: Apply lessons to future patches
|
||||
|
||||
## Resources
|
||||
|
||||
- **Gerrit**: https://review.typo3.org
|
||||
- **Forger Search**: https://forger.typo3.com
|
||||
- **Gerrit Documentation**: https://review.typo3.org/Documentation/
|
||||
- **Slack**: #typo3-cms-coredev
|
||||
|
||||
## Quick Command Reference
|
||||
|
||||
| Action | Command |
|
||||
|--------|---------|
|
||||
| Push new patch | `git push origin HEAD:refs/for/main` |
|
||||
| Update patch | `git commit --amend && git push origin HEAD:refs/for/main` |
|
||||
| Rebase on main | `git fetch origin && git rebase origin/main` |
|
||||
| Abort rebase | `git rebase --abort` |
|
||||
| Continue rebase | `git rebase --continue` |
|
||||
| Cherry-pick | `git fetch origin refs/changes/XX/XXXX/X && git cherry-pick FETCH_HEAD` |
|
||||
| Push as WIP | `git push origin HEAD:refs/for/main%wip` |
|
||||
| Test SSH | `ssh -p 29418 <user>@review.typo3.org` |
|
||||
1025
skills/typo3-core-contributions/references/troubleshooting.md
Normal file
1025
skills/typo3-core-contributions/references/troubleshooting.md
Normal file
File diff suppressed because it is too large
Load Diff
186
skills/typo3-core-contributions/scripts/create-commit-message.py
Executable file
186
skills/typo3-core-contributions/scripts/create-commit-message.py
Executable file
@@ -0,0 +1,186 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
TYPO3 Core Contribution Commit Message Generator
|
||||
Creates properly formatted commit messages following TYPO3 standards
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import re
|
||||
from typing import Optional
|
||||
|
||||
|
||||
COMMIT_TYPES = {
|
||||
'BUGFIX': 'Bug fixes',
|
||||
'FEATURE': 'New features (main branch only)',
|
||||
'TASK': 'Refactoring, cleanup, miscellaneous',
|
||||
'DOCS': 'Documentation changes',
|
||||
'SECURITY': 'Security vulnerability fixes'
|
||||
}
|
||||
|
||||
BREAKING_CHANGE_PREFIX = '[!!!]'
|
||||
|
||||
|
||||
def validate_subject(subject: str, has_breaking: bool) -> tuple[bool, Optional[str]]:
|
||||
"""Validate subject line against TYPO3 rules"""
|
||||
max_length = 52 if not has_breaking else 47 # Account for [!!!] prefix
|
||||
|
||||
if len(subject) > 72:
|
||||
return False, "Subject line exceeds 72 characters (absolute limit)"
|
||||
|
||||
if len(subject) > max_length:
|
||||
return False, f"Subject line exceeds {max_length} characters (recommended limit)"
|
||||
|
||||
if not subject[0].isupper():
|
||||
return False, "Subject must start with uppercase letter"
|
||||
|
||||
if subject.endswith('.'):
|
||||
return False, "Subject should not end with a period"
|
||||
|
||||
# Check for imperative mood (simple heuristic)
|
||||
past_tense_endings = ['ed', 'ing']
|
||||
first_word = subject.split()[0].lower()
|
||||
if any(first_word.endswith(end) for end in past_tense_endings):
|
||||
return False, f"Use imperative mood ('{first_word}' appears to be past/present continuous tense)"
|
||||
|
||||
return True, None
|
||||
|
||||
|
||||
def wrap_text(text: str, width: int = 72) -> str:
|
||||
"""Wrap text at specified width"""
|
||||
words = text.split()
|
||||
lines = []
|
||||
current_line = []
|
||||
current_length = 0
|
||||
|
||||
for word in words:
|
||||
word_length = len(word)
|
||||
if current_length + word_length + len(current_line) > width:
|
||||
if current_line:
|
||||
lines.append(' '.join(current_line))
|
||||
current_line = [word]
|
||||
current_length = word_length
|
||||
else:
|
||||
current_line.append(word)
|
||||
current_length += word_length
|
||||
|
||||
if current_line:
|
||||
lines.append(' '.join(current_line))
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def parse_releases(releases_str: str) -> list[str]:
|
||||
"""Parse comma-separated release versions"""
|
||||
releases = [r.strip() for r in releases_str.split(',')]
|
||||
# Validate format
|
||||
valid_releases = []
|
||||
for release in releases:
|
||||
if release == 'main' or re.match(r'^\d+\.\d+$', release):
|
||||
valid_releases.append(release)
|
||||
else:
|
||||
print(f"Warning: Invalid release format '{release}', skipping")
|
||||
return valid_releases
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Generate TYPO3-compliant commit messages',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog='''
|
||||
Examples:
|
||||
%(prog)s --issue 105737 --type BUGFIX
|
||||
%(prog)s --issue 105737 --type FEATURE --breaking
|
||||
%(prog)s --type TASK --related 12345,12346
|
||||
'''
|
||||
)
|
||||
|
||||
parser.add_argument('--issue', type=int, help='Forge issue number')
|
||||
parser.add_argument('--related', help='Related issue numbers (comma-separated)')
|
||||
parser.add_argument('--type', choices=COMMIT_TYPES.keys(), required=True,
|
||||
help='Commit type')
|
||||
parser.add_argument('--breaking', action='store_true',
|
||||
help='Mark as breaking change (adds [!!!] prefix)')
|
||||
parser.add_argument('--releases', default='main',
|
||||
help='Target releases (comma-separated, e.g., "main, 13.4, 12.4")')
|
||||
parser.add_argument('--output', help='Output file (default: print to stdout)')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Interactive mode
|
||||
print("=== TYPO3 Commit Message Generator ===\n")
|
||||
|
||||
# Get subject line
|
||||
print(f"Commit Type: [{args.type}]")
|
||||
if args.breaking:
|
||||
print(f"Breaking Change: Yes (will add {BREAKING_CHANGE_PREFIX} prefix)")
|
||||
print()
|
||||
|
||||
subject = input("Enter subject line (max 52 chars, imperative mood): ").strip()
|
||||
|
||||
# Validate subject
|
||||
valid, error = validate_subject(subject, args.breaking)
|
||||
if not valid:
|
||||
print(f"\n❌ Error: {error}")
|
||||
sys.exit(1)
|
||||
|
||||
# Get description
|
||||
print("\nEnter description (explain how and why, not what).")
|
||||
print("Press Ctrl+D (Linux/Mac) or Ctrl+Z (Windows) when done:")
|
||||
description_lines = []
|
||||
try:
|
||||
while True:
|
||||
line = input()
|
||||
description_lines.append(line)
|
||||
except EOFError:
|
||||
pass
|
||||
|
||||
description = '\n'.join(description_lines).strip()
|
||||
if description:
|
||||
description = wrap_text(description)
|
||||
|
||||
# Build commit message
|
||||
type_prefix = f"{BREAKING_CHANGE_PREFIX}{args.type}" if args.breaking else args.type
|
||||
message = f"[{type_prefix}] {subject}\n\n"
|
||||
|
||||
if description:
|
||||
message += f"{description}\n\n"
|
||||
|
||||
# Add footer
|
||||
if args.issue:
|
||||
message += f"Resolves: #{args.issue}\n"
|
||||
|
||||
if args.related:
|
||||
related_issues = [f"#{num.strip()}" for num in args.related.split(',')]
|
||||
for issue in related_issues:
|
||||
message += f"Related: {issue}\n"
|
||||
|
||||
releases = parse_releases(args.releases)
|
||||
if releases:
|
||||
message += f"Releases: {', '.join(releases)}\n"
|
||||
|
||||
# Output
|
||||
print("\n" + "="*60)
|
||||
print("Generated Commit Message:")
|
||||
print("="*60)
|
||||
print(message)
|
||||
print("="*60)
|
||||
print("\nNote: Change-Id will be added automatically by git hook")
|
||||
print("="*60)
|
||||
|
||||
if args.output:
|
||||
with open(args.output, 'w') as f:
|
||||
f.write(message)
|
||||
print(f"\n✓ Commit message saved to: {args.output}")
|
||||
print(f" Use: git commit -F {args.output}")
|
||||
else:
|
||||
print("\nTo use this message:")
|
||||
print(" 1. Copy the message above")
|
||||
print(" 2. Run: git commit")
|
||||
print(" 3. Paste into your editor")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
192
skills/typo3-core-contributions/scripts/create-forge-issue.sh
Executable file
192
skills/typo3-core-contributions/scripts/create-forge-issue.sh
Executable file
@@ -0,0 +1,192 @@
|
||||
#!/bin/bash
|
||||
# Create TYPO3 Forge issue via Redmine REST API
|
||||
#
|
||||
# Usage:
|
||||
# 1. Get your API key from https://forge.typo3.org/my/account
|
||||
# 2. Set environment variable: export FORGE_API_KEY="your-key-here"
|
||||
# 3. Run: ./scripts/create-forge-issue.sh
|
||||
|
||||
set -e
|
||||
|
||||
# Color output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Check for API key
|
||||
if [ -z "$FORGE_API_KEY" ]; then
|
||||
echo -e "${RED}Error: FORGE_API_KEY environment variable not set${NC}"
|
||||
echo ""
|
||||
echo "Get your API key from: https://forge.typo3.org/my/account"
|
||||
echo "Then set it: export FORGE_API_KEY=\"your-key-here\""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for required tools
|
||||
for tool in curl jq; do
|
||||
if ! command -v $tool &> /dev/null; then
|
||||
echo -e "${RED}Error: $tool is required but not installed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo -e "${GREEN}TYPO3 Forge Issue Creator${NC}"
|
||||
echo ""
|
||||
|
||||
# Interactive prompts
|
||||
read -p "Issue subject (title): " SUBJECT
|
||||
if [ -z "$SUBJECT" ]; then
|
||||
echo -e "${RED}Error: Subject is required${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Issue description (multi-line, press Ctrl+D when done):"
|
||||
DESCRIPTION=$(cat)
|
||||
if [ -z "$DESCRIPTION" ]; then
|
||||
echo -e "${RED}Error: Description is required${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Select tracker type:"
|
||||
echo " 1) Bug"
|
||||
echo " 2) Feature"
|
||||
echo " 3) Task"
|
||||
read -p "Choice [1]: " TRACKER_CHOICE
|
||||
TRACKER_CHOICE=${TRACKER_CHOICE:-1}
|
||||
|
||||
case $TRACKER_CHOICE in
|
||||
1) TRACKER_ID=1; TRACKER_NAME="Bug" ;;
|
||||
2) TRACKER_ID=2; TRACKER_NAME="Feature" ;;
|
||||
3) TRACKER_ID=4; TRACKER_NAME="Task" ;;
|
||||
*) echo -e "${RED}Invalid choice${NC}"; exit 1 ;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
echo "Select priority:"
|
||||
echo " 1) Must have"
|
||||
echo " 2) Should have (recommended)"
|
||||
echo " 3) Could have"
|
||||
read -p "Choice [2]: " PRIORITY_CHOICE
|
||||
PRIORITY_CHOICE=${PRIORITY_CHOICE:-2}
|
||||
|
||||
case $PRIORITY_CHOICE in
|
||||
1) PRIORITY_ID=3; PRIORITY_NAME="Must have" ;;
|
||||
2) PRIORITY_ID=4; PRIORITY_NAME="Should have" ;;
|
||||
3) PRIORITY_ID=5; PRIORITY_NAME="Could have" ;;
|
||||
*) echo -e "${RED}Invalid choice${NC}"; exit 1 ;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
read -p "TYPO3 version affected (e.g., 13, 14) [13]: " TYPO3_VERSION
|
||||
TYPO3_VERSION=${TYPO3_VERSION:-13}
|
||||
|
||||
echo ""
|
||||
echo "Select category (common ones, or enter ID manually):"
|
||||
echo " 1) Miscellaneous (975)"
|
||||
echo " 2) Backend API (971)"
|
||||
echo " 3) Backend User Interface (972)"
|
||||
echo " 4) Frontend (977)"
|
||||
echo " 5) Database API (974)"
|
||||
echo " 6) Indexed Search (1000)"
|
||||
echo " 7) Extension Manager (976)"
|
||||
echo " 8) Documentation (1004)"
|
||||
echo " 9) Enter category ID manually"
|
||||
read -p "Choice [1]: " CATEGORY_CHOICE
|
||||
CATEGORY_CHOICE=${CATEGORY_CHOICE:-1}
|
||||
|
||||
case $CATEGORY_CHOICE in
|
||||
1) CATEGORY_ID=975; CATEGORY_NAME="Miscellaneous" ;;
|
||||
2) CATEGORY_ID=971; CATEGORY_NAME="Backend API" ;;
|
||||
3) CATEGORY_ID=972; CATEGORY_NAME="Backend User Interface" ;;
|
||||
4) CATEGORY_ID=977; CATEGORY_NAME="Frontend" ;;
|
||||
5) CATEGORY_ID=974; CATEGORY_NAME="Database API" ;;
|
||||
6) CATEGORY_ID=1000; CATEGORY_NAME="Indexed Search" ;;
|
||||
7) CATEGORY_ID=976; CATEGORY_NAME="Extension Manager" ;;
|
||||
8) CATEGORY_ID=1004; CATEGORY_NAME="Documentation" ;;
|
||||
9)
|
||||
read -p "Enter category ID: " CATEGORY_ID
|
||||
CATEGORY_NAME="Custom ($CATEGORY_ID)"
|
||||
;;
|
||||
*) echo -e "${RED}Invalid choice${NC}"; exit 1 ;;
|
||||
esac
|
||||
|
||||
# Optional tags
|
||||
echo ""
|
||||
read -p "Tags (comma-separated, optional): " TAGS
|
||||
|
||||
# Summary
|
||||
echo ""
|
||||
echo -e "${YELLOW}Summary:${NC}"
|
||||
echo " Tracker: $TRACKER_NAME"
|
||||
echo " Subject: $SUBJECT"
|
||||
echo " Priority: $PRIORITY_NAME"
|
||||
echo " Category: $CATEGORY_NAME"
|
||||
echo " TYPO3 Version: $TYPO3_VERSION"
|
||||
[ -n "$TAGS" ] && echo " Tags: $TAGS"
|
||||
echo ""
|
||||
|
||||
read -p "Create this issue? [Y/n]: " CONFIRM
|
||||
CONFIRM=${CONFIRM:-Y}
|
||||
|
||||
if [[ ! $CONFIRM =~ ^[Yy]$ ]]; then
|
||||
echo "Cancelled."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Build JSON payload
|
||||
JSON_PAYLOAD=$(jq -n \
|
||||
--arg subject "$SUBJECT" \
|
||||
--arg description "$DESCRIPTION" \
|
||||
--argjson tracker "$TRACKER_ID" \
|
||||
--argjson category "$CATEGORY_ID" \
|
||||
--argjson priority "$PRIORITY_ID" \
|
||||
--arg typo3_version "$TYPO3_VERSION" \
|
||||
--arg tags "$TAGS" \
|
||||
'{
|
||||
issue: {
|
||||
project_id: "typo3cms-core",
|
||||
subject: $subject,
|
||||
description: $description,
|
||||
tracker_id: $tracker,
|
||||
category_id: $category,
|
||||
priority_id: $priority,
|
||||
custom_fields: [
|
||||
{id: 4, value: $typo3_version}
|
||||
] + (if $tags != "" then [{id: 3, value: $tags}] else [] end)
|
||||
}
|
||||
}')
|
||||
|
||||
# Create issue
|
||||
echo ""
|
||||
echo -e "${YELLOW}Creating issue...${NC}"
|
||||
|
||||
RESPONSE=$(curl -s -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Redmine-API-Key: $FORGE_API_KEY" \
|
||||
-d "$JSON_PAYLOAD" \
|
||||
https://forge.typo3.org/issues.json)
|
||||
|
||||
# Check for errors
|
||||
if echo "$RESPONSE" | jq -e '.errors' > /dev/null 2>&1; then
|
||||
echo -e "${RED}Error creating issue:${NC}"
|
||||
echo "$RESPONSE" | jq -r '.errors[]'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract issue details
|
||||
ISSUE_ID=$(echo "$RESPONSE" | jq -r '.issue.id')
|
||||
ISSUE_URL="https://forge.typo3.org/issues/${ISSUE_ID}"
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}Success! Issue created:${NC}"
|
||||
echo ""
|
||||
echo " Issue #: $ISSUE_ID"
|
||||
echo " URL: $ISSUE_URL"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Next steps:${NC}"
|
||||
echo " 1. Use in commit message: ${GREEN}Resolves: #${ISSUE_ID}${NC}"
|
||||
echo " 2. Create feature branch: ${GREEN}git checkout -b feature/${ISSUE_ID}-description${NC}"
|
||||
echo ""
|
||||
102
skills/typo3-core-contributions/scripts/query-forge-metadata.sh
Executable file
102
skills/typo3-core-contributions/scripts/query-forge-metadata.sh
Executable file
@@ -0,0 +1,102 @@
|
||||
#!/bin/bash
|
||||
# Query TYPO3 Forge project metadata via Redmine REST API
|
||||
#
|
||||
# Usage:
|
||||
# 1. Get your API key from https://forge.typo3.org/my/account
|
||||
# 2. Set environment variable: export FORGE_API_KEY="your-key-here"
|
||||
# 3. Run: ./scripts/query-forge-metadata.sh [categories|trackers|all]
|
||||
|
||||
set -e
|
||||
|
||||
# Color output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Check for API key
|
||||
if [ -z "$FORGE_API_KEY" ]; then
|
||||
echo -e "${RED}Error: FORGE_API_KEY environment variable not set${NC}"
|
||||
echo ""
|
||||
echo "Get your API key from: https://forge.typo3.org/my/account"
|
||||
echo "Then set it: export FORGE_API_KEY=\"your-key-here\""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for required tools
|
||||
for tool in curl jq; do
|
||||
if ! command -v $tool &> /dev/null; then
|
||||
echo -e "${RED}Error: $tool is required but not installed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Parse arguments
|
||||
QUERY_TYPE=${1:-all}
|
||||
|
||||
# Fetch project metadata
|
||||
echo -e "${YELLOW}Fetching TYPO3 Core project metadata...${NC}"
|
||||
echo ""
|
||||
|
||||
RESPONSE=$(curl -s \
|
||||
-H "X-Redmine-API-Key: $FORGE_API_KEY" \
|
||||
https://forge.typo3.org/projects/typo3cms-core.json)
|
||||
|
||||
# Check for errors
|
||||
if echo "$RESPONSE" | jq -e '.errors' > /dev/null 2>&1; then
|
||||
echo -e "${RED}Error querying Forge:${NC}"
|
||||
echo "$RESPONSE" | jq -r '.errors[]'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Display trackers
|
||||
if [[ "$QUERY_TYPE" == "trackers" || "$QUERY_TYPE" == "all" ]]; then
|
||||
echo -e "${GREEN}=== Trackers ===${NC}"
|
||||
echo ""
|
||||
echo "$RESPONSE" | jq -r '.project.trackers[] | "\(.id)\t\(.name)"' | \
|
||||
awk -F'\t' '{printf " %-4s %s\n", $1, $2}'
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Display categories
|
||||
if [[ "$QUERY_TYPE" == "categories" || "$QUERY_TYPE" == "all" ]]; then
|
||||
echo -e "${GREEN}=== Issue Categories ===${NC}"
|
||||
echo ""
|
||||
echo "$RESPONSE" | jq -r '.project.issue_categories[] | "\(.id)\t\(.name)"' | \
|
||||
awk -F'\t' '{printf " %-6s %s\n", $1, $2}'
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Display usage examples
|
||||
if [[ "$QUERY_TYPE" == "all" ]]; then
|
||||
echo -e "${BLUE}=== Usage Examples ===${NC}"
|
||||
echo ""
|
||||
echo "Create bug in Backend API category:"
|
||||
echo ' curl -X POST \'
|
||||
echo ' -H "Content-Type: application/json" \'
|
||||
echo ' -H "X-Redmine-API-Key: $FORGE_API_KEY" \'
|
||||
echo ' -d '"'"'{'
|
||||
echo ' "issue": {'
|
||||
echo ' "project_id": "typo3cms-core",'
|
||||
echo ' "subject": "Issue title",'
|
||||
echo ' "description": "Description",'
|
||||
echo ' "tracker_id": 1,'
|
||||
echo ' "category_id": 971,'
|
||||
echo ' "priority_id": 4,'
|
||||
echo ' "custom_fields": [{"id": 4, "value": "13"}]'
|
||||
echo ' }'
|
||||
echo ' }'"'"' \'
|
||||
echo ' https://forge.typo3.org/issues.json'
|
||||
echo ""
|
||||
echo "Or use the interactive script:"
|
||||
echo " ./scripts/create-forge-issue.sh"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Save to file if requested
|
||||
if [[ "$2" == "--save" ]]; then
|
||||
OUTPUT_FILE="forge-metadata-$(date +%Y%m%d).json"
|
||||
echo "$RESPONSE" > "$OUTPUT_FILE"
|
||||
echo -e "${GREEN}Metadata saved to: $OUTPUT_FILE${NC}"
|
||||
fi
|
||||
433
skills/typo3-core-contributions/scripts/setup-typo3-coredev.sh
Executable file
433
skills/typo3-core-contributions/scripts/setup-typo3-coredev.sh
Executable file
@@ -0,0 +1,433 @@
|
||||
#!/bin/bash
|
||||
# TYPO3 Core Development Environment Setup Script
|
||||
# Based on proven production workflow
|
||||
# Creates complete DDEV-based TYPO3 Core development environment
|
||||
|
||||
set -e
|
||||
|
||||
# Colors
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Print functions
|
||||
print_header() {
|
||||
echo -e "\n${BLUE}===================================================${NC}"
|
||||
echo -e "${BLUE}$1${NC}"
|
||||
echo -e "${BLUE}===================================================${NC}\n"
|
||||
}
|
||||
|
||||
print_step() {
|
||||
echo -e "${GREEN}➜${NC} $1"
|
||||
}
|
||||
|
||||
print_info() {
|
||||
echo -e "${YELLOW}ℹ${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}✗${NC} $1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}✓${NC} $1"
|
||||
}
|
||||
|
||||
# Check prerequisites
|
||||
check_prerequisites() {
|
||||
print_header "Checking Prerequisites"
|
||||
|
||||
local missing_prereqs=false
|
||||
|
||||
# Check Git
|
||||
if command -v git &> /dev/null; then
|
||||
print_success "Git: $(git --version)"
|
||||
else
|
||||
print_error "Git not found. Please install Git first."
|
||||
missing_prereqs=true
|
||||
fi
|
||||
|
||||
# Check DDEV
|
||||
if command -v ddev &> /dev/null; then
|
||||
print_success "DDEV: $(ddev version | head -n1)"
|
||||
else
|
||||
print_error "DDEV not found. Please install DDEV first: https://ddev.readthedocs.io/"
|
||||
missing_prereqs=true
|
||||
fi
|
||||
|
||||
# Check Docker
|
||||
if command -v docker &> /dev/null; then
|
||||
if docker ps &> /dev/null; then
|
||||
print_success "Docker: Running"
|
||||
else
|
||||
print_error "Docker not running. Please start Docker."
|
||||
missing_prereqs=true
|
||||
fi
|
||||
else
|
||||
print_error "Docker not found. DDEV requires Docker."
|
||||
missing_prereqs=true
|
||||
fi
|
||||
|
||||
if [ "$missing_prereqs" = true ]; then
|
||||
print_error "Missing prerequisites. Please install required tools and try again."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Gather user input
|
||||
gather_input() {
|
||||
print_header "Configuration"
|
||||
|
||||
# Project name
|
||||
read -p "Project name (e.g., t3coredev-14-php8-4): " PROJECT_NAME
|
||||
if [ -z "$PROJECT_NAME" ]; then
|
||||
PROJECT_NAME="t3coredev-14-php8-4"
|
||||
print_info "Using default: $PROJECT_NAME"
|
||||
fi
|
||||
|
||||
# Git user name
|
||||
read -p "Your name for Git commits: " GIT_NAME
|
||||
while [ -z "$GIT_NAME" ]; do
|
||||
print_error "Name is required"
|
||||
read -p "Your name for Git commits: " GIT_NAME
|
||||
done
|
||||
|
||||
# Git email
|
||||
read -p "Your email for Git commits: " GIT_EMAIL
|
||||
while [ -z "$GIT_EMAIL" ]; do
|
||||
print_error "Email is required"
|
||||
read -p "Your email for Git commits: " GIT_EMAIL
|
||||
done
|
||||
|
||||
# Gerrit username
|
||||
read -p "Your Gerrit username (review.typo3.org): " GERRIT_USER
|
||||
while [ -z "$GERRIT_USER" ]; do
|
||||
print_error "Gerrit username is required"
|
||||
read -p "Your Gerrit username: " GERRIT_USER
|
||||
done
|
||||
|
||||
# PHP version
|
||||
read -p "PHP version (8.2, 8.3, 8.4) [default: 8.4]: " PHP_VERSION
|
||||
if [ -z "$PHP_VERSION" ]; then
|
||||
PHP_VERSION="8.4"
|
||||
fi
|
||||
|
||||
# Timezone
|
||||
read -p "Timezone [default: Europe/Vienna]: " TIMEZONE
|
||||
if [ -z "$TIMEZONE" ]; then
|
||||
TIMEZONE="Europe/Vienna"
|
||||
fi
|
||||
|
||||
# Admin password
|
||||
read -sp "TYPO3 admin password: " ADMIN_PASSWORD
|
||||
echo
|
||||
while [ -z "$ADMIN_PASSWORD" ]; do
|
||||
print_error "Admin password is required"
|
||||
read -sp "TYPO3 admin password: " ADMIN_PASSWORD
|
||||
echo
|
||||
done
|
||||
|
||||
# Confirm
|
||||
echo -e "\n${YELLOW}Configuration Summary:${NC}"
|
||||
echo " Project: $PROJECT_NAME"
|
||||
echo " Git Name: $GIT_NAME"
|
||||
echo " Git Email: $GIT_EMAIL"
|
||||
echo " Gerrit User: $GERRIT_USER"
|
||||
echo " PHP Version: $PHP_VERSION"
|
||||
echo " Timezone: $TIMEZONE"
|
||||
echo
|
||||
|
||||
read -p "Proceed with setup? (y/n): " CONFIRM
|
||||
if [[ ! "$CONFIRM" =~ ^[Yy]$ ]]; then
|
||||
print_info "Setup cancelled."
|
||||
exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
# Create project directory
|
||||
create_project_dir() {
|
||||
print_header "Creating Project Directory"
|
||||
|
||||
if [ -d "$PROJECT_NAME" ]; then
|
||||
print_error "Directory $PROJECT_NAME already exists!"
|
||||
read -p "Delete and recreate? (y/n): " DELETE_CONFIRM
|
||||
if [[ "$DELETE_CONFIRM" =~ ^[Yy]$ ]]; then
|
||||
rm -rf "$PROJECT_NAME"
|
||||
print_success "Deleted existing directory"
|
||||
else
|
||||
print_error "Cannot proceed with existing directory"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir -p "$PROJECT_NAME"
|
||||
cd "$PROJECT_NAME"
|
||||
print_success "Created and entered directory: $PROJECT_NAME"
|
||||
}
|
||||
|
||||
# Clone TYPO3 repository
|
||||
clone_repository() {
|
||||
print_header "Cloning TYPO3 Repository"
|
||||
|
||||
print_step "Cloning from GitHub..."
|
||||
if git clone git@github.com:typo3/typo3 . 2>&1 | grep -q "Permission denied\|Could not"; then
|
||||
print_error "Failed to clone via SSH. Trying HTTPS..."
|
||||
rm -rf .git
|
||||
if ! git clone https://github.com/typo3/typo3.git . ; then
|
||||
print_error "Failed to clone repository"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
print_success "Repository cloned successfully"
|
||||
}
|
||||
|
||||
# Configure Git
|
||||
configure_git() {
|
||||
print_header "Configuring Git"
|
||||
|
||||
print_step "Setting user identity..."
|
||||
git config user.name "$GIT_NAME"
|
||||
git config user.email "$GIT_EMAIL"
|
||||
print_success "User identity configured"
|
||||
|
||||
print_step "Enabling automatic rebase..."
|
||||
git config branch.autosetuprebase remote
|
||||
print_success "Automatic rebase enabled"
|
||||
|
||||
print_step "Installing git hooks..."
|
||||
if [ -f "Build/git-hooks/commit-msg" ]; then
|
||||
cp Build/git-hooks/commit-msg .git/hooks/commit-msg
|
||||
chmod +x .git/hooks/commit-msg
|
||||
print_success "Commit-msg hook installed"
|
||||
else
|
||||
print_error "Commit-msg hook not found in Build/git-hooks/"
|
||||
fi
|
||||
|
||||
print_step "Configuring Gerrit remote..."
|
||||
git config remote.origin.pushurl "ssh://${GERRIT_USER}@review.typo3.org:29418/Packages/TYPO3.CMS.git"
|
||||
git config remote.origin.push "+refs/heads/main:refs/for/main"
|
||||
print_success "Gerrit remote configured"
|
||||
|
||||
# Test Gerrit connection
|
||||
print_step "Testing Gerrit SSH connection..."
|
||||
if timeout 5 ssh -p 29418 -o StrictHostKeyChecking=no -o BatchMode=yes "${GERRIT_USER}@review.typo3.org" gerrit version &>/dev/null; then
|
||||
print_success "Gerrit connection successful"
|
||||
else
|
||||
print_error "Cannot connect to Gerrit. Please verify your SSH keys are configured."
|
||||
print_info "Continue anyway? SSH key might need configuration."
|
||||
read -p "Continue? (y/n): " CONTINUE
|
||||
if [[ ! "$CONTINUE" =~ ^[Yy]$ ]]; then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Configure DDEV
|
||||
configure_ddev() {
|
||||
print_header "Configuring DDEV"
|
||||
|
||||
print_step "Setting project type..."
|
||||
ddev config --project-type typo3 -y
|
||||
|
||||
print_step "Configuring timezone..."
|
||||
ddev config --timezone "$TIMEZONE"
|
||||
|
||||
print_step "Setting PHP version..."
|
||||
ddev config --php-version="$PHP_VERSION"
|
||||
|
||||
print_step "Configuring webserver..."
|
||||
ddev config --webserver-type=apache-fpm
|
||||
|
||||
print_step "Setting database version..."
|
||||
ddev config --database=mariadb:10.6
|
||||
|
||||
print_step "Adding environment variables..."
|
||||
ddev config --web-environment-add="TYPO3_CONTEXT=Development/Ddev"
|
||||
ddev config --web-environment-add="COMPOSER_ROOT_VERSION=14.0.x-dev"
|
||||
|
||||
print_success "DDEV configured successfully"
|
||||
}
|
||||
|
||||
# Start DDEV
|
||||
start_ddev() {
|
||||
print_header "Starting DDEV"
|
||||
|
||||
print_step "Starting containers..."
|
||||
if ddev start; then
|
||||
print_success "DDEV started successfully"
|
||||
ddev describe
|
||||
else
|
||||
print_error "Failed to start DDEV"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Install dependencies
|
||||
install_dependencies() {
|
||||
print_header "Installing Dependencies"
|
||||
|
||||
print_step "Running Composer install via runTests.sh..."
|
||||
if ./Build/Scripts/runTests.sh -s composerInstall; then
|
||||
print_success "Dependencies installed"
|
||||
else
|
||||
print_error "Failed to install dependencies"
|
||||
print_info "Trying alternative method..."
|
||||
if ddev composer install; then
|
||||
print_success "Dependencies installed via ddev composer"
|
||||
else
|
||||
print_error "Failed to install dependencies"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Setup TYPO3
|
||||
setup_typo3() {
|
||||
print_header "Setting Up TYPO3"
|
||||
|
||||
print_step "Creating installation trigger files..."
|
||||
ddev exec 'touch /var/www/html/FIRST_INSTALL'
|
||||
ddev exec 'touch /var/www/html/typo3conf/ENABLE_INSTALL_TOOL'
|
||||
ddev exec 'echo "KEEP_FILE" > /var/www/html/typo3conf/ENABLE_INSTALL_TOOL'
|
||||
print_success "Trigger files created"
|
||||
|
||||
print_step "Running TYPO3 setup..."
|
||||
if ddev typo3 setup \
|
||||
--driver=mysqli \
|
||||
--host=db \
|
||||
--port=3306 \
|
||||
--dbname=db \
|
||||
--username=db \
|
||||
--password=db \
|
||||
--admin-username=backenduser \
|
||||
--admin-user-password="$ADMIN_PASSWORD" \
|
||||
--admin-email="$GIT_EMAIL" \
|
||||
--project-name="TYPO3 Core Dev v14 PHP ${PHP_VERSION}" \
|
||||
--no-interaction \
|
||||
--server-type=apache \
|
||||
--force; then
|
||||
print_success "TYPO3 setup completed"
|
||||
else
|
||||
print_error "TYPO3 setup failed"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Activate extensions
|
||||
activate_extensions() {
|
||||
print_header "Activating Extensions"
|
||||
|
||||
print_step "Setting up extensions..."
|
||||
ddev typo3 extension:setup
|
||||
|
||||
print_step "Activating indexed_search..."
|
||||
ddev typo3 extension:activate indexed_search
|
||||
|
||||
print_step "Activating styleguide..."
|
||||
ddev typo3 extension:activate styleguide
|
||||
|
||||
print_step "Activating scheduler..."
|
||||
ddev typo3 extension:activate scheduler
|
||||
|
||||
print_success "Extensions activated"
|
||||
}
|
||||
|
||||
# Setup backend groups
|
||||
setup_backend_groups() {
|
||||
print_header "Setting Up Backend User Groups"
|
||||
|
||||
print_step "Creating default backend groups..."
|
||||
if ddev typo3 setup:begroups:default --groups=Both; then
|
||||
print_success "Backend groups configured"
|
||||
else
|
||||
print_error "Failed to setup backend groups"
|
||||
fi
|
||||
}
|
||||
|
||||
# Generate test data
|
||||
generate_test_data() {
|
||||
print_header "Generating Test Data"
|
||||
|
||||
read -p "Generate styleguide test data? (y/n): " GENERATE_DATA
|
||||
if [[ "$GENERATE_DATA" =~ ^[Yy]$ ]]; then
|
||||
print_step "Generating TCA examples..."
|
||||
ddev typo3 styleguide:generate --create -- tca
|
||||
|
||||
print_step "Generating frontend system template..."
|
||||
ddev typo3 styleguide:generate --create -- frontend-systemplate
|
||||
|
||||
print_success "Test data generated"
|
||||
else
|
||||
print_info "Skipping test data generation"
|
||||
fi
|
||||
}
|
||||
|
||||
# Final steps
|
||||
finalize() {
|
||||
print_header "Setup Complete!"
|
||||
|
||||
print_success "TYPO3 Core development environment is ready!"
|
||||
echo
|
||||
echo -e "${GREEN}Project Details:${NC}"
|
||||
echo " Name: $PROJECT_NAME"
|
||||
echo " URL: https://${PROJECT_NAME}.ddev.site"
|
||||
echo " Backend: https://${PROJECT_NAME}.ddev.site/typo3"
|
||||
echo " Admin User: backenduser"
|
||||
echo " Admin Password: [the password you entered]"
|
||||
echo
|
||||
echo -e "${GREEN}Next Steps:${NC}"
|
||||
echo " 1. Open backend: ddev launch /typo3"
|
||||
echo " 2. Run tests: ./Build/Scripts/runTests.sh -s unit"
|
||||
echo " 3. Create branch: git checkout -b feature/your-feature"
|
||||
echo " 4. Make changes and commit with proper message"
|
||||
echo " 5. Push to Gerrit: git push origin HEAD:refs/for/main"
|
||||
echo
|
||||
echo -e "${GREEN}Useful Commands:${NC}"
|
||||
echo " ddev start - Start project"
|
||||
echo " ddev stop - Stop project"
|
||||
echo " ddev restart - Restart project"
|
||||
echo " ddev ssh - SSH into container"
|
||||
echo " ddev typo3 cache:flush - Clear TYPO3 caches"
|
||||
echo " ddev logs -f - Follow logs"
|
||||
echo
|
||||
|
||||
read -p "Open TYPO3 backend now? (y/n): " OPEN_BACKEND
|
||||
if [[ "$OPEN_BACKEND" =~ ^[Yy]$ ]]; then
|
||||
ddev launch /typo3
|
||||
fi
|
||||
}
|
||||
|
||||
# Main execution
|
||||
main() {
|
||||
clear
|
||||
print_header "TYPO3 Core Development Setup"
|
||||
|
||||
echo "This script will set up a complete TYPO3 Core development environment."
|
||||
echo "It will:"
|
||||
echo " - Clone TYPO3 Core repository"
|
||||
echo " - Configure Git for Gerrit submissions"
|
||||
echo " - Set up DDEV with optimal settings"
|
||||
echo " - Install TYPO3 with test data"
|
||||
echo " - Activate development extensions"
|
||||
echo
|
||||
|
||||
check_prerequisites
|
||||
gather_input
|
||||
create_project_dir
|
||||
clone_repository
|
||||
configure_git
|
||||
configure_ddev
|
||||
start_ddev
|
||||
install_dependencies
|
||||
setup_typo3
|
||||
activate_extensions
|
||||
setup_backend_groups
|
||||
generate_test_data
|
||||
finalize
|
||||
}
|
||||
|
||||
# Run main function
|
||||
main
|
||||
256
skills/typo3-core-contributions/scripts/validate-commit-message.py
Executable file
256
skills/typo3-core-contributions/scripts/validate-commit-message.py
Executable file
@@ -0,0 +1,256 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
TYPO3 Commit Message Validator
|
||||
Validates commit messages against TYPO3 contribution standards
|
||||
"""
|
||||
|
||||
import sys
|
||||
import re
|
||||
import argparse
|
||||
from typing import List, Tuple
|
||||
|
||||
|
||||
VALID_TYPES = ['BUGFIX', 'FEATURE', 'TASK', 'DOCS', 'SECURITY']
|
||||
BREAKING_PREFIX = '[!!!]'
|
||||
|
||||
|
||||
class CommitMessageValidator:
|
||||
def __init__(self, message: str):
|
||||
self.message = message
|
||||
self.lines = message.split('\n')
|
||||
self.errors = []
|
||||
self.warnings = []
|
||||
|
||||
def validate(self) -> Tuple[bool, List[str], List[str]]:
|
||||
"""Run all validation checks"""
|
||||
self.check_subject_line()
|
||||
self.check_blank_line()
|
||||
self.check_footer()
|
||||
self.check_change_id()
|
||||
|
||||
return len(self.errors) == 0, self.errors, self.warnings
|
||||
|
||||
def check_subject_line(self):
|
||||
"""Validate the subject line"""
|
||||
if not self.lines:
|
||||
self.errors.append("Commit message is empty")
|
||||
return
|
||||
|
||||
subject = self.lines[0]
|
||||
|
||||
# Check for commit type
|
||||
type_pattern = r'^\[(?:\[!!!\])?(BUGFIX|FEATURE|TASK|DOCS|SECURITY)\]'
|
||||
match = re.match(type_pattern, subject)
|
||||
|
||||
if not match:
|
||||
self.errors.append(
|
||||
f"Subject must start with commit type: {', '.join(f'[{t}]' for t in VALID_TYPES)}"
|
||||
)
|
||||
return
|
||||
|
||||
commit_type = match.group(1)
|
||||
|
||||
# Check for breaking change prefix
|
||||
if subject.startswith('[!!!]'):
|
||||
if commit_type == 'BUGFIX':
|
||||
self.warnings.append(
|
||||
"Breaking changes are unusual for BUGFIX. Consider using FEATURE or TASK"
|
||||
)
|
||||
|
||||
# Extract subject without type prefix
|
||||
subject_without_type = re.sub(type_pattern, '', subject).strip()
|
||||
|
||||
# Check length
|
||||
if len(subject) > 72:
|
||||
self.errors.append(
|
||||
f"Subject line is {len(subject)} characters (max 72). Current: {len(subject)}"
|
||||
)
|
||||
elif len(subject) > 52:
|
||||
self.warnings.append(
|
||||
f"Subject line is {len(subject)} characters (recommended max 52)"
|
||||
)
|
||||
|
||||
# Check capitalization
|
||||
if subject_without_type and not subject_without_type[0].isupper():
|
||||
self.errors.append("Subject description must start with uppercase letter")
|
||||
|
||||
# Check for period at end
|
||||
if subject.endswith('.'):
|
||||
self.errors.append("Subject line should not end with a period")
|
||||
|
||||
# Check for imperative mood (heuristic)
|
||||
if subject_without_type:
|
||||
first_word = subject_without_type.split()[0].lower()
|
||||
if first_word.endswith('ed') or first_word.endswith('ing'):
|
||||
self.warnings.append(
|
||||
f"Use imperative mood: '{first_word}' may not be imperative. "
|
||||
"Use 'Fix' not 'Fixed' or 'Fixing'"
|
||||
)
|
||||
|
||||
def check_blank_line(self):
|
||||
"""Check for blank line after subject"""
|
||||
if len(self.lines) < 2:
|
||||
return # Only subject line, no body
|
||||
|
||||
if len(self.lines) >= 2 and self.lines[1] != '':
|
||||
self.errors.append("Second line must be blank (separate subject from body)")
|
||||
|
||||
def check_footer(self):
|
||||
"""Check footer tags"""
|
||||
footer_pattern = r'^(Resolves|Related|Releases|Depends|Reverts):\s*'
|
||||
|
||||
has_resolves = False
|
||||
has_releases = False
|
||||
has_change_id = False
|
||||
|
||||
for i, line in enumerate(self.lines):
|
||||
if re.match(footer_pattern, line):
|
||||
# Check format: should have colon followed by space
|
||||
if not re.match(r'^[A-Z][a-z]+:\s+', line):
|
||||
self.errors.append(
|
||||
f"Line {i+1}: Footer tag must have colon followed by space: '{line}'"
|
||||
)
|
||||
|
||||
# Check specific tags
|
||||
if line.startswith('Resolves:'):
|
||||
has_resolves = True
|
||||
# Validate issue number format
|
||||
if not re.match(r'^Resolves:\s+#\d+', line):
|
||||
self.errors.append(
|
||||
f"Line {i+1}: Resolves must reference issue number: 'Resolves: #12345'"
|
||||
)
|
||||
|
||||
elif line.startswith('Related:'):
|
||||
if not re.match(r'^Related:\s+#\d+', line):
|
||||
self.errors.append(
|
||||
f"Line {i+1}: Related must reference issue number: 'Related: #12345'"
|
||||
)
|
||||
|
||||
elif line.startswith('Releases:'):
|
||||
has_releases = True
|
||||
# Validate releases format
|
||||
releases_value = line.split(':', 1)[1].strip()
|
||||
releases = [r.strip() for r in releases_value.split(',')]
|
||||
for release in releases:
|
||||
if release != 'main' and not re.match(r'^\d+\.\d+$', release):
|
||||
self.errors.append(
|
||||
f"Line {i+1}: Invalid release format '{release}'. "
|
||||
"Use 'main' or version like '13.4'"
|
||||
)
|
||||
|
||||
elif line.startswith('Change-Id:'):
|
||||
has_change_id = True
|
||||
|
||||
# Warnings for missing tags
|
||||
if not has_resolves:
|
||||
self.warnings.append(
|
||||
"No 'Resolves: #<issue>' tag found. Required for features and tasks."
|
||||
)
|
||||
|
||||
if not has_releases:
|
||||
self.warnings.append(
|
||||
"No 'Releases:' tag found. Required to specify target versions."
|
||||
)
|
||||
|
||||
def check_change_id(self):
|
||||
"""Check for Change-Id"""
|
||||
change_id_pattern = r'^Change-Id:\s+I[a-f0-9]{40}$'
|
||||
has_change_id = any(re.match(change_id_pattern, line) for line in self.lines)
|
||||
|
||||
if not has_change_id:
|
||||
self.warnings.append(
|
||||
"No Change-Id found. It will be added automatically by git commit-msg hook."
|
||||
)
|
||||
|
||||
def check_line_length(self):
|
||||
"""Check body line lengths"""
|
||||
for i, line in enumerate(self.lines[2:], start=3): # Skip subject and blank line
|
||||
if line.startswith(('Resolves:', 'Related:', 'Releases:', 'Change-Id:', 'Depends:', 'Reverts:')):
|
||||
continue # Skip footer
|
||||
|
||||
if len(line) > 72:
|
||||
# Allow URLs to be longer
|
||||
if not re.search(r'https?://', line):
|
||||
self.warnings.append(
|
||||
f"Line {i}: Length {len(line)} exceeds 72 characters"
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Validate TYPO3 commit messages',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter
|
||||
)
|
||||
|
||||
parser.add_argument('--file', '-f', help='File containing commit message')
|
||||
parser.add_argument('--message', '-m', help='Commit message string')
|
||||
parser.add_argument('--strict', action='store_true',
|
||||
help='Treat warnings as errors')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Get message
|
||||
if args.file:
|
||||
try:
|
||||
with open(args.file, 'r') as f:
|
||||
message = f.read()
|
||||
except FileNotFoundError:
|
||||
print(f"Error: File not found: {args.file}")
|
||||
return 1
|
||||
elif args.message:
|
||||
message = args.message
|
||||
else:
|
||||
# Read from last commit
|
||||
try:
|
||||
import subprocess
|
||||
result = subprocess.run(
|
||||
['git', 'log', '-1', '--pretty=%B'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
message = result.stdout
|
||||
except subprocess.CalledProcessError:
|
||||
print("Error: Could not read last commit message")
|
||||
print("Usage: Provide --file or --message, or run in a git repository")
|
||||
return 1
|
||||
|
||||
# Validate
|
||||
validator = CommitMessageValidator(message)
|
||||
is_valid, errors, warnings = validator.validate()
|
||||
|
||||
# Print results
|
||||
print("=" * 60)
|
||||
print("TYPO3 Commit Message Validation")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
if errors:
|
||||
print("❌ ERRORS:")
|
||||
for error in errors:
|
||||
print(f" • {error}")
|
||||
print()
|
||||
|
||||
if warnings:
|
||||
print("⚠️ WARNINGS:")
|
||||
for warning in warnings:
|
||||
print(f" • {warning}")
|
||||
print()
|
||||
|
||||
if not errors and not warnings:
|
||||
print("✅ Commit message is valid!")
|
||||
elif not errors:
|
||||
print("✅ No errors found (warnings can be ignored)")
|
||||
else:
|
||||
print("❌ Validation failed. Please fix errors above.")
|
||||
|
||||
print("=" * 60)
|
||||
|
||||
# Exit code
|
||||
if errors or (args.strict and warnings):
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
181
skills/typo3-core-contributions/scripts/verify-prerequisites.sh
Executable file
181
skills/typo3-core-contributions/scripts/verify-prerequisites.sh
Executable file
@@ -0,0 +1,181 @@
|
||||
#!/bin/bash
|
||||
# TYPO3 Core Contribution Prerequisites Checker
|
||||
# Verifies accounts, git configuration, and development environment setup
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo "================================================"
|
||||
echo "TYPO3 Core Contribution Prerequisites Check"
|
||||
echo "================================================"
|
||||
echo
|
||||
|
||||
# Track overall status
|
||||
ALL_CHECKS_PASSED=true
|
||||
|
||||
# Function to print status
|
||||
print_status() {
|
||||
if [ "$1" = "pass" ]; then
|
||||
echo -e "${GREEN}✓${NC} $2"
|
||||
elif [ "$1" = "fail" ]; then
|
||||
echo -e "${RED}✗${NC} $2"
|
||||
ALL_CHECKS_PASSED=false
|
||||
elif [ "$1" = "warn" ]; then
|
||||
echo -e "${YELLOW}⚠${NC} $2"
|
||||
fi
|
||||
}
|
||||
|
||||
# 1. Check Git installation
|
||||
echo "1. Checking Git installation..."
|
||||
if command -v git &> /dev/null; then
|
||||
GIT_VERSION=$(git --version)
|
||||
print_status "pass" "Git installed: $GIT_VERSION"
|
||||
else
|
||||
print_status "fail" "Git not installed"
|
||||
fi
|
||||
echo
|
||||
|
||||
# 2. Check Git user configuration
|
||||
echo "2. Checking Git user configuration..."
|
||||
GIT_USER_NAME=$(git config --global user.name 2>/dev/null || echo "")
|
||||
GIT_USER_EMAIL=$(git config --global user.email 2>/dev/null || echo "")
|
||||
|
||||
if [ -n "$GIT_USER_NAME" ] && [ -n "$GIT_USER_EMAIL" ]; then
|
||||
print_status "pass" "Git user configured: $GIT_USER_NAME <$GIT_USER_EMAIL>"
|
||||
else
|
||||
print_status "fail" "Git user not configured. Run:"
|
||||
echo " git config --global user.name \"Your Name\""
|
||||
echo " git config --global user.email \"your-email@example.org\""
|
||||
fi
|
||||
echo
|
||||
|
||||
# 2a. Verify Git email matches Gerrit account
|
||||
echo "2a. Verifying Git email against Gerrit..."
|
||||
if [ -n "$GIT_USER_EMAIL" ]; then
|
||||
echo " Your Git email: $GIT_USER_EMAIL"
|
||||
print_status "warn" " IMPORTANT: Verify this email is registered at:"
|
||||
echo " https://review.typo3.org/settings#EmailAddresses"
|
||||
echo " Gerrit will reject pushes if email doesn't match!"
|
||||
fi
|
||||
echo
|
||||
|
||||
# 3. Check if in TYPO3 repository
|
||||
echo "3. Checking TYPO3 repository..."
|
||||
if [ -d ".git" ]; then
|
||||
REPO_URL=$(git config --get remote.origin.url 2>/dev/null || echo "")
|
||||
if [[ "$REPO_URL" == *"typo3"* ]]; then
|
||||
print_status "pass" "In TYPO3 repository"
|
||||
|
||||
# Check TYPO3-specific git config
|
||||
echo " Checking TYPO3-specific configuration..."
|
||||
|
||||
# Check auto-rebase
|
||||
AUTO_REBASE=$(git config --get branch.autosetuprebase 2>/dev/null || echo "")
|
||||
if [ "$AUTO_REBASE" = "remote" ]; then
|
||||
print_status "pass" " Auto-rebase configured"
|
||||
else
|
||||
print_status "fail" " Auto-rebase not configured. Run: git config branch.autosetuprebase remote"
|
||||
fi
|
||||
|
||||
# Check Gerrit push URL
|
||||
PUSH_URL=$(git config --get remote.origin.pushurl 2>/dev/null || echo "")
|
||||
if [[ "$PUSH_URL" == *"review.typo3.org"* ]]; then
|
||||
print_status "pass" " Gerrit push URL configured"
|
||||
else
|
||||
print_status "fail" " Gerrit push URL not configured. Run:"
|
||||
echo " git config remote.origin.pushurl ssh://<USERNAME>@review.typo3.org:29418/Packages/TYPO3.CMS.git"
|
||||
fi
|
||||
|
||||
# Check push refspec
|
||||
PUSH_REFSPEC=$(git config --get remote.origin.push 2>/dev/null || echo "")
|
||||
if [[ "$PUSH_REFSPEC" == *"refs/for/main"* ]]; then
|
||||
print_status "pass" " Push refspec configured for Gerrit"
|
||||
else
|
||||
print_status "fail" " Push refspec not configured. Run:"
|
||||
echo " git config remote.origin.push +refs/heads/main:refs/for/main"
|
||||
fi
|
||||
|
||||
else
|
||||
print_status "warn" "In git repository but not TYPO3. URL: $REPO_URL"
|
||||
fi
|
||||
else
|
||||
print_status "warn" "Not in a git repository (run from TYPO3 repo root)"
|
||||
fi
|
||||
echo
|
||||
|
||||
# 4. Check Git hooks
|
||||
echo "4. Checking Git hooks..."
|
||||
if [ -f ".git/hooks/commit-msg" ]; then
|
||||
print_status "pass" "commit-msg hook installed"
|
||||
else
|
||||
print_status "fail" "commit-msg hook not installed. Run: composer gerrit:setup"
|
||||
fi
|
||||
|
||||
if [ -f ".git/hooks/pre-commit" ]; then
|
||||
print_status "pass" "pre-commit hook installed"
|
||||
else
|
||||
print_status "warn" "pre-commit hook not installed (optional but recommended)"
|
||||
fi
|
||||
echo
|
||||
|
||||
# 5. Check SSH connection to Gerrit
|
||||
echo "5. Checking Gerrit SSH connection..."
|
||||
if timeout 5 ssh -p 29418 -o StrictHostKeyChecking=no -o BatchMode=yes review.typo3.org gerrit version &>/dev/null; then
|
||||
print_status "pass" "Gerrit SSH connection successful"
|
||||
else
|
||||
print_status "fail" "Cannot connect to Gerrit via SSH. Check your SSH keys and Gerrit setup"
|
||||
fi
|
||||
echo
|
||||
|
||||
# 6. Check Composer
|
||||
echo "6. Checking Composer installation..."
|
||||
if command -v composer &> /dev/null; then
|
||||
COMPOSER_VERSION=$(composer --version 2>/dev/null | head -n1)
|
||||
print_status "pass" "Composer installed: $COMPOSER_VERSION"
|
||||
else
|
||||
print_status "warn" "Composer not found (needed for running tests and gerrit:setup)"
|
||||
fi
|
||||
echo
|
||||
|
||||
# 7. Check PHP
|
||||
echo "7. Checking PHP installation..."
|
||||
if command -v php &> /dev/null; then
|
||||
PHP_VERSION=$(php -v | head -n1)
|
||||
print_status "pass" "PHP installed: $PHP_VERSION"
|
||||
else
|
||||
print_status "warn" "PHP not found (needed for development and testing)"
|
||||
fi
|
||||
echo
|
||||
|
||||
# 8. Check DDEV (optional)
|
||||
echo "8. Checking DDEV installation (optional)..."
|
||||
if command -v ddev &> /dev/null; then
|
||||
DDEV_VERSION=$(ddev version | head -n1)
|
||||
print_status "pass" "DDEV installed: $DDEV_VERSION"
|
||||
else
|
||||
print_status "warn" "DDEV not found (recommended for development environment)"
|
||||
fi
|
||||
echo
|
||||
|
||||
# Final Summary
|
||||
echo "================================================"
|
||||
if [ "$ALL_CHECKS_PASSED" = true ]; then
|
||||
echo -e "${GREEN}✓ All critical checks passed!${NC}"
|
||||
echo "You're ready to contribute to TYPO3 Core."
|
||||
else
|
||||
echo -e "${RED}✗ Some checks failed.${NC}"
|
||||
echo "Please address the issues above before contributing."
|
||||
fi
|
||||
echo "================================================"
|
||||
|
||||
# Exit with appropriate code
|
||||
if [ "$ALL_CHECKS_PASSED" = true ]; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
19
skills/typo3-ddev/.gitignore
vendored
Normal file
19
skills/typo3-ddev/.gitignore
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
# macOS
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.bak
|
||||
skill-completion.txt
|
||||
21
skills/typo3-ddev/LICENSE
Normal file
21
skills/typo3-ddev/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Netresearch DTT GmbH
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
427
skills/typo3-ddev/README.md
Normal file
427
skills/typo3-ddev/README.md
Normal file
@@ -0,0 +1,427 @@
|
||||
# TYPO3 DDEV Skill
|
||||
|
||||
> A Claude Code skill for automating DDEV environment setup in TYPO3 extension projects
|
||||
|
||||
[](https://typo3.org/)
|
||||
[](https://ddev.com/)
|
||||
[](LICENSE)
|
||||
|
||||
## Overview
|
||||
|
||||
This skill helps TYPO3 extension developers quickly set up a complete DDEV development environment with multiple TYPO3 versions. Instead of manually configuring DDEV, this skill automates the entire process - from detecting your extension metadata to generating all configuration files and installing TYPO3.
|
||||
|
||||
### What It Does
|
||||
|
||||
- ✅ Detects TYPO3 extension projects automatically
|
||||
- ✅ Extracts extension metadata (key, package name, namespace)
|
||||
- ✅ Generates complete DDEV configuration
|
||||
- ✅ Creates multi-version TYPO3 testing environment (11.5, 12.4, 13.4 LTS)
|
||||
- ✅ Provides backend and frontend access with preconfigured credentials
|
||||
- ✅ Includes custom DDEV commands for easy TYPO3 installation
|
||||
|
||||
### Who It's For
|
||||
|
||||
- TYPO3 extension developers
|
||||
- Teams working on TYPO3 extensions
|
||||
- Developers needing to test extensions across multiple TYPO3 versions
|
||||
- Anyone wanting a quick, reproducible TYPO3 development environment
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before using this skill, ensure you have:
|
||||
|
||||
- [DDEV](https://ddev.readthedocs.io/en/stable/users/install/ddev-installation/) installed
|
||||
- [Docker](https://www.docker.com/get-started) running
|
||||
- A TYPO3 extension project (with `ext_emconf.php` or `composer.json`)
|
||||
- [Claude Code](https://claude.com/claude-code) installed
|
||||
|
||||
## Installation
|
||||
|
||||
### Option 1: Install as Claude Code Skill
|
||||
|
||||
```bash
|
||||
# Clone this repository into your Claude Code skills directory
|
||||
cd ~/.claude/skills/ # or your custom skills directory
|
||||
git clone https://github.com/netresearch/typo3-ddev-skill.git
|
||||
|
||||
# The skill is now available in Claude Code
|
||||
```
|
||||
|
||||
### Option 2: Manual Setup
|
||||
|
||||
1. Download this repository
|
||||
2. Copy the `skill.md` file to your project or reference it when needed
|
||||
3. Use the templates manually by copying from the `templates/` directory
|
||||
|
||||
## Usage
|
||||
|
||||
### In Claude Code
|
||||
|
||||
Once installed, simply invoke the skill in your TYPO3 extension project:
|
||||
|
||||
```
|
||||
/typo3-ddev
|
||||
```
|
||||
|
||||
Or ask Claude:
|
||||
|
||||
```
|
||||
Set up DDEV for my TYPO3 extension
|
||||
```
|
||||
|
||||
The skill will:
|
||||
|
||||
1. Validate prerequisites (DDEV, Docker, TYPO3 extension structure)
|
||||
2. Extract your extension metadata
|
||||
3. Confirm configuration with you
|
||||
4. Generate all `.ddev/` files with proper values
|
||||
5. Guide you through starting DDEV and installing TYPO3
|
||||
|
||||
### What You'll Get
|
||||
|
||||
After setup, you'll have:
|
||||
|
||||
```
|
||||
project-root/
|
||||
├── .ddev/
|
||||
│ ├── config.yaml
|
||||
│ ├── docker-compose.web.yaml
|
||||
│ ├── apache/
|
||||
│ │ └── apache-site.conf
|
||||
│ ├── web-build/
|
||||
│ │ └── Dockerfile
|
||||
│ └── commands/
|
||||
│ └── web/
|
||||
│ ├── install-v11
|
||||
│ ├── install-v12
|
||||
│ ├── install-v13
|
||||
│ └── install-all
|
||||
├── Classes/
|
||||
├── Configuration/
|
||||
├── ext_emconf.php
|
||||
└── composer.json
|
||||
```
|
||||
|
||||
### Accessing Your Environment
|
||||
|
||||
Once installed, access TYPO3 at:
|
||||
|
||||
**Overview Dashboard:**
|
||||
```
|
||||
https://your-ext.ddev.site/
|
||||
```
|
||||
|
||||
**TYPO3 11.5 LTS:**
|
||||
- Frontend: `https://v11.your-ext.ddev.site/`
|
||||
- Backend: `https://v11.your-ext.ddev.site/typo3/`
|
||||
|
||||
**TYPO3 12.4 LTS:**
|
||||
- Frontend: `https://v12.your-ext.ddev.site/`
|
||||
- Backend: `https://v12.your-ext.ddev.site/typo3/`
|
||||
|
||||
**TYPO3 13.4 LTS:**
|
||||
- Frontend: `https://v13.your-ext.ddev.site/`
|
||||
- Backend: `https://v13.your-ext.ddev.site/typo3/`
|
||||
|
||||
### Backend Credentials
|
||||
|
||||
```
|
||||
Username: admin
|
||||
Password: Password:joh316
|
||||
```
|
||||
|
||||
## Custom DDEV Commands
|
||||
|
||||
The skill creates these custom commands:
|
||||
|
||||
```bash
|
||||
# Install specific TYPO3 version
|
||||
ddev install-v11 # TYPO3 11.5 LTS
|
||||
ddev install-v12 # TYPO3 12.4 LTS
|
||||
ddev install-v13 # TYPO3 13.4 LTS
|
||||
|
||||
# Install all versions at once
|
||||
ddev install-all
|
||||
|
||||
# Install Introduction Package (demo content)
|
||||
ddev install-introduction v13
|
||||
```
|
||||
|
||||
## Adding Demo Content
|
||||
|
||||
Test your extension with realistic content using the TYPO3 Introduction Package:
|
||||
|
||||
```bash
|
||||
# Install Introduction Package
|
||||
ddev install-introduction v13
|
||||
```
|
||||
|
||||
**Includes:**
|
||||
- 86+ pages with example content
|
||||
- 226+ content elements (text, images, forms, tables)
|
||||
- Multi-language support (EN, DE, DA)
|
||||
- Bootstrap Package responsive theme
|
||||
- Perfect for testing RTE features
|
||||
|
||||
## Extension Auto-Configuration
|
||||
|
||||
For extensions needing additional setup (RTE config, TSconfig, TypoScript), create a custom configuration command:
|
||||
|
||||
```bash
|
||||
# Copy template
|
||||
cp .ddev/templates/commands/web/configure-extension.optional \
|
||||
.ddev/commands/web/configure-myext
|
||||
|
||||
# Customize for your extension's needs
|
||||
# Add: RTE YAML, Page TSConfig, TypoScript, site package setup
|
||||
|
||||
# Run after TYPO3 installation
|
||||
ddev configure-myext v13
|
||||
```
|
||||
|
||||
**Use Cases:**
|
||||
- **RTE/CKEditor plugins** - Configure toolbar, import plugin YAML
|
||||
- **Backend modules** - Set up TSconfig, permissions
|
||||
- **Frontend plugins** - TypoScript configuration, example content
|
||||
|
||||
**Pattern Benefits:**
|
||||
- One-command post-install setup
|
||||
- Consistent team configuration
|
||||
- Includes demo content automatically
|
||||
- Reduces manual configuration errors
|
||||
|
||||
See `SKILL.md` for detailed examples and template structure.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Database Already Exists
|
||||
|
||||
If reinstalling TYPO3:
|
||||
|
||||
```bash
|
||||
ddev mysql -e "DROP DATABASE IF EXISTS v13; CREATE DATABASE v13;"
|
||||
ddev install-v13
|
||||
```
|
||||
|
||||
### Services Not Starting
|
||||
|
||||
Check and restart services:
|
||||
|
||||
```bash
|
||||
docker ps --filter "name=ddev-your-ext"
|
||||
ddev restart
|
||||
```
|
||||
|
||||
### Extension Not Visible
|
||||
|
||||
Flush caches:
|
||||
|
||||
```bash
|
||||
ddev exec -d /var/www/html/v13 vendor/bin/typo3 cache:flush
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
The setup creates a unique multi-version environment:
|
||||
|
||||
- **Extension Source**: Mounted at `/var/www/{{EXTENSION_KEY}}` (your project root)
|
||||
- **TYPO3 Installations**: Separate directories for each version (`/var/www/html/v11`, `v12`, `v13`)
|
||||
- **Extension Installation**: Installed via Composer path repository in each TYPO3 version
|
||||
- **Persistent Data**: Docker volumes for each TYPO3 version database and files
|
||||
|
||||
This architecture allows you to:
|
||||
- Develop extension code in your project root
|
||||
- Test immediately across all TYPO3 versions
|
||||
- Keep TYPO3 installations separate and clean
|
||||
- Avoid committing TYPO3 core files to your extension repository
|
||||
|
||||
## Configuration
|
||||
|
||||
### Supported TYPO3 Versions
|
||||
|
||||
By default, the skill supports:
|
||||
- TYPO3 11.5 LTS (PHP 8.0+)
|
||||
- TYPO3 12.4 LTS (PHP 8.1+)
|
||||
- TYPO3 13.4 LTS (PHP 8.2+)
|
||||
|
||||
PHP version is set to 8.2 for maximum compatibility.
|
||||
|
||||
### Customization
|
||||
|
||||
After generation, you can customize:
|
||||
|
||||
**PHP Version** (`.ddev/config.yaml`):
|
||||
```yaml
|
||||
php_version: "8.3" # Change to 8.1 or 8.3 if needed
|
||||
```
|
||||
|
||||
**XDebug** (enable/disable):
|
||||
```bash
|
||||
ddev xdebug on # Enable
|
||||
ddev xdebug off # Disable
|
||||
```
|
||||
|
||||
**Database** (Tiered Selection: SQLite/MariaDB/PostgreSQL/MySQL):
|
||||
|
||||
The skill uses **intelligent tiered database selection** based on extension complexity:
|
||||
|
||||
**🎯 Tier 1: SQLite (Simple Extensions - Development Optimized)**
|
||||
|
||||
For extensions using only TYPO3 Core APIs (no custom tables, no raw SQL):
|
||||
|
||||
```yaml
|
||||
# No .ddev/config.yaml database config needed
|
||||
# TYPO3 installation automatically uses SQLite
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- ⚡ Startup: 5-10 seconds faster per ddev start
|
||||
- 💾 RAM: 900 MB saved (no container)
|
||||
- 💿 Disk: 744 MB saved
|
||||
- 🔒 Perfect v11/v12/v13 isolation
|
||||
|
||||
**⚠️ Development ONLY** - Never use SQLite in production. Switch to MariaDB for final testing.
|
||||
|
||||
**🔧 Tier 2: MariaDB 10.11 (Complex Extensions - Production Parity)**
|
||||
|
||||
For extensions with custom tables, raw SQL, or unknown complexity:
|
||||
|
||||
```yaml
|
||||
# Default for complex extensions (.ddev/config.yaml)
|
||||
database:
|
||||
type: mariadb
|
||||
version: "10.11"
|
||||
```
|
||||
|
||||
**Why MariaDB 10.11?** Production standard (95% hosting), extension compatibility, 13-36% faster than MySQL 8.
|
||||
|
||||
**🌐 Tier 3 & 4: Specialized Databases**
|
||||
|
||||
```yaml
|
||||
# PostgreSQL 16 (for GIS, analytics, full-text search)
|
||||
database:
|
||||
type: postgres
|
||||
version: "16"
|
||||
|
||||
# MariaDB 11 (forward-looking performance)
|
||||
database:
|
||||
type: mariadb
|
||||
version: "11.4"
|
||||
|
||||
# MySQL 8.0 (corporate/Oracle ecosystem)
|
||||
database:
|
||||
type: mysql
|
||||
version: "8.0"
|
||||
```
|
||||
|
||||
**For detailed rationale**, see `docs/adr/0002-mariadb-default-with-database-alternatives.md`.
|
||||
|
||||
**Caching Service** (Valkey or Redis):
|
||||
|
||||
The skill provides Valkey 8 as the default caching service (open source, future-proof):
|
||||
|
||||
```bash
|
||||
# Default: Valkey 8
|
||||
cp .ddev/templates/docker-compose.services.yaml.optional .ddev/docker-compose.services.yaml
|
||||
|
||||
# Alternative: Redis 7 (for legacy production parity)
|
||||
cp .ddev/templates/docker-compose.services-redis.yaml.optional .ddev/docker-compose.services.yaml
|
||||
|
||||
# Restart DDEV
|
||||
ddev restart
|
||||
```
|
||||
|
||||
**Why Valkey?** True open source (BSD-3), AWS/Google/Oracle backing, 30% smaller than Redis 8, cost-effective. See `docs/adr/0001-valkey-default-with-redis-alternative.md` for details.
|
||||
|
||||
**Additional Services** (add to `.ddev/docker-compose.services.yaml`):
|
||||
```yaml
|
||||
# The services template includes: Valkey/Redis, MailPit, Ofelia
|
||||
# See SKILL.md for complete documentation
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### DDEV Won't Start
|
||||
|
||||
**Port Conflicts:**
|
||||
```bash
|
||||
# Check what's using ports 80/443
|
||||
sudo lsof -i :80
|
||||
sudo lsof -i :443
|
||||
|
||||
# Option 1: Stop conflicting services
|
||||
# Option 2: Change ports in .ddev/config.yaml
|
||||
router_http_port: "8080"
|
||||
router_https_port: "8443"
|
||||
```
|
||||
|
||||
### Installation Fails
|
||||
|
||||
**Check Composer Issues:**
|
||||
```bash
|
||||
ddev ssh
|
||||
composer diagnose
|
||||
```
|
||||
|
||||
**View Installation Logs:**
|
||||
```bash
|
||||
ddev logs
|
||||
```
|
||||
|
||||
**Retry Installation:**
|
||||
```bash
|
||||
# For specific version
|
||||
ddev ssh
|
||||
rm -rf /var/www/html/v13/*
|
||||
exit
|
||||
ddev install-v13
|
||||
```
|
||||
|
||||
### Extension Not Appearing
|
||||
|
||||
**Verify Extension Key:**
|
||||
```bash
|
||||
ddev ssh
|
||||
echo $EXTENSION_KEY # Should match your ext_emconf.php
|
||||
```
|
||||
|
||||
**Check Composer Repository:**
|
||||
```bash
|
||||
ddev ssh
|
||||
cd /var/www/html/v13
|
||||
composer config repositories
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions welcome! Please:
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
||||
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
||||
4. Push to the branch (`git push origin feature/amazing-feature`)
|
||||
5. Open a Pull Request
|
||||
|
||||
## Credits
|
||||
|
||||
This skill is based on the excellent work by [Armin Vieweg](https://github.com/a-r-m-i-n) in [ddev-for-typo3-extensions](https://github.com/a-r-m-i-n/ddev-for-typo3-extensions).
|
||||
|
||||
### Additional Resources
|
||||
|
||||
- [DDEV Documentation](https://ddev.readthedocs.io/)
|
||||
- [TYPO3 Extension Development](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/Index.html)
|
||||
- [TYPO3 DDEV Setup Guide](https://docs.typo3.org/m/typo3/tutorial-getting-started/main/en-us/Installation/UsingDdev.html)
|
||||
|
||||
## License
|
||||
|
||||
MIT License - see [LICENSE](LICENSE) file for details.
|
||||
|
||||
## Support
|
||||
|
||||
- **Issues**: [GitHub Issues](https://github.com/netresearch/typo3-ddev-skill/issues)
|
||||
- **Discussions**: [GitHub Discussions](https://github.com/netresearch/typo3-ddev-skill/discussions)
|
||||
- **TYPO3 Slack**: #ddev channel
|
||||
|
||||
---
|
||||
|
||||
**Made with ❤️ for the TYPO3 Community**
|
||||
903
skills/typo3-ddev/SKILL.md
Normal file
903
skills/typo3-ddev/SKILL.md
Normal file
@@ -0,0 +1,903 @@
|
||||
---
|
||||
name: typo3-ddev
|
||||
description: "Automate DDEV environment setup for TYPO3 extension development. Use when setting up local development environment, configuring DDEV for TYPO3 extensions, or creating multi-version TYPO3 testing environments. Covers: DDEV configuration generation, TYPO3 11.5/12.4/13.4 LTS installation, custom DDEV commands, Apache vhost setup, Docker volume management, and .gitignore best practices (avoid double-ignore anti-pattern). Provides complete automation from metadata detection to ready-to-use TYPO3 backend access."
|
||||
license: MIT License - see LICENSE file
|
||||
metadata:
|
||||
author: Netresearch
|
||||
repository: https://github.com/netresearch/typo3-ddev-skill
|
||||
version: 1.6.0
|
||||
tags:
|
||||
- typo3
|
||||
- ddev
|
||||
- docker
|
||||
- development
|
||||
- testing
|
||||
- devops
|
||||
platforms:
|
||||
- linux
|
||||
- macos
|
||||
- windows
|
||||
typo3-versions:
|
||||
- 11.5-lts
|
||||
- 12.4-lts
|
||||
- 13.4-lts
|
||||
---
|
||||
# TYPO3 DDEV Setup Skill
|
||||
|
||||
This skill automates DDEV environment setup for TYPO3 extension development projects. It creates a complete TYPO3 development environment for testing and developing extensions across multiple TYPO3 versions.
|
||||
|
||||
## Purpose
|
||||
|
||||
This skill automates the setup of DDEV for TYPO3 extension development projects. It creates a multi-version TYPO3 testing environment where the extension can be developed and tested across TYPO3 11.5 LTS, 12.4 LTS, and 13.4 LTS simultaneously.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when:
|
||||
- Developer has a TYPO3 extension project and wants to set up DDEV
|
||||
- Project contains `ext_emconf.php` or is identified as a TYPO3 extension in `composer.json`
|
||||
- Developer needs to test extension across multiple TYPO3 versions
|
||||
- Quick development environment spin-up is needed
|
||||
|
||||
## Prerequisites Validation
|
||||
|
||||
Before executing ANY DDEV commands, perform comprehensive prerequisite validation:
|
||||
|
||||
1. **Run validation script**: Execute `scripts/validate-prerequisites.sh` to check:
|
||||
- Docker daemon status (must be running)
|
||||
- Docker CLI version (requires >= 20.10)
|
||||
- Docker Compose version (requires >= 2.0)
|
||||
- DDEV installation (must be present)
|
||||
- TYPO3 extension project structure (must be valid)
|
||||
|
||||
2. **If validation fails**: Consult `references/prerequisites-validation.md` for platform-specific installation instructions (Linux/WSL2, macOS, Windows), version requirements, manual validation steps, and troubleshooting
|
||||
|
||||
3. **Report results**: Display validation status to user with specific instructions for any missing prerequisites
|
||||
|
||||
Always validate on the FIRST DDEV command in a session to catch environment issues early.
|
||||
|
||||
## Step-by-Step Workflow
|
||||
|
||||
### Step 1: Extract Extension Metadata
|
||||
|
||||
Scan the project to extract:
|
||||
|
||||
1. **Extension Key** (e.g., `my_ext`):
|
||||
- From `ext_emconf.php`: Look for the array key or filename pattern
|
||||
- From `composer.json`: Look for `extra.typo3/cms.extension-key`
|
||||
- Fallback: Ask user to provide
|
||||
|
||||
2. **Composer Package Name** (e.g., `vendor/my-ext`):
|
||||
- From `composer.json`: Look for `name` field
|
||||
- Fallback: Construct from extension key or ask user
|
||||
|
||||
3. **Vendor Namespace** (e.g., `Vendor\MyExt`):
|
||||
- From `composer.json`: Look for `autoload.psr-4` keys
|
||||
- Fallback: Ask user to provide
|
||||
|
||||
4. **Extension Name** (PascalCase, e.g., `MyExt`):
|
||||
- Convert extension key to PascalCase
|
||||
- Or extract from namespace
|
||||
|
||||
### Step 2: Confirm Configuration
|
||||
|
||||
Present extracted values to user and confirm:
|
||||
|
||||
```
|
||||
Detected TYPO3 Extension Configuration:
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Extension Key: my_ext
|
||||
Package Name: vendor/my-ext
|
||||
DDEV Sitename: my-ext
|
||||
Vendor Namespace: Vendor\MyExt
|
||||
TYPO3 Versions: 11.5, 12.4, 13.4
|
||||
PHP Version: 8.2
|
||||
|
||||
Is this correct? (y/n)
|
||||
```
|
||||
|
||||
Allow user to adjust any values if needed.
|
||||
|
||||
### Step 3: Generate DDEV Configuration
|
||||
|
||||
Create the following directory structure and files:
|
||||
|
||||
```
|
||||
.ddev/
|
||||
├── config.yaml
|
||||
├── docker-compose.web.yaml
|
||||
├── apache/
|
||||
│ └── apache-site.conf
|
||||
├── web-build/
|
||||
│ └── Dockerfile
|
||||
└── commands/
|
||||
└── web/
|
||||
├── install-v11
|
||||
├── install-v12
|
||||
├── install-v13
|
||||
└── install-all
|
||||
.envrc
|
||||
```
|
||||
|
||||
#### 3.1: .ddev/config.yaml
|
||||
|
||||
Replace these variables:
|
||||
- `{{DDEV_SITENAME}}` → DDEV sitename (e.g., `my-ext`)
|
||||
|
||||
```yaml
|
||||
name: {{DDEV_SITENAME}}
|
||||
type: php
|
||||
docroot: ""
|
||||
no_project_mount: true
|
||||
php_version: "8.2"
|
||||
composer_version: "2"
|
||||
webserver_type: apache-fpm
|
||||
router_http_port: "80"
|
||||
router_https_port: "443"
|
||||
xdebug_enabled: false
|
||||
additional_hostnames:
|
||||
- docs.{{DDEV_SITENAME}}
|
||||
- v11.{{DDEV_SITENAME}}
|
||||
- v12.{{DDEV_SITENAME}}
|
||||
- v13.{{DDEV_SITENAME}}
|
||||
additional_fqdns: []
|
||||
use_dns_when_possible: true
|
||||
```
|
||||
|
||||
#### 3.2: .ddev/docker-compose.web.yaml
|
||||
|
||||
Replace these variables:
|
||||
- `{{EXTENSION_KEY}}` → Extension key with underscores (e.g., `my_ext`)
|
||||
- `{{PACKAGE_NAME}}` → Composer package name (e.g., `vendor/my-ext`)
|
||||
- `{{DDEV_SITENAME}}` → DDEV sitename for volumes
|
||||
|
||||
```yaml
|
||||
services:
|
||||
web:
|
||||
environment:
|
||||
- EXTENSION_KEY={{EXTENSION_KEY}}
|
||||
- PACKAGE_NAME={{PACKAGE_NAME}}
|
||||
|
||||
# TYPO3 v11 and v12 config
|
||||
- TYPO3_INSTALL_DB_DRIVER=mysqli
|
||||
- TYPO3_INSTALL_DB_USER=root
|
||||
- TYPO3_INSTALL_DB_PASSWORD=root
|
||||
- TYPO3_INSTALL_DB_HOST=db
|
||||
- TYPO3_INSTALL_DB_UNIX_SOCKET=
|
||||
- TYPO3_INSTALL_DB_USE_EXISTING=0
|
||||
- TYPO3_INSTALL_ADMIN_USER=admin
|
||||
- TYPO3_INSTALL_ADMIN_PASSWORD=Password:joh316
|
||||
- TYPO3_INSTALL_SITE_NAME=EXT:{{EXTENSION_KEY}} Dev Environment
|
||||
- TYPO3_INSTALL_SITE_SETUP_TYPE=site
|
||||
- TYPO3_INSTALL_WEB_SERVER_CONFIG=apache
|
||||
|
||||
# TYPO3 v13 config
|
||||
- TYPO3_DB_DRIVER=mysqli
|
||||
- TYPO3_DB_USERNAME=root
|
||||
- TYPO3_DB_PASSWORD=root
|
||||
- TYPO3_DB_HOST=db
|
||||
- TYPO3_SETUP_ADMIN_EMAIL=admin@example.com
|
||||
- TYPO3_SETUP_ADMIN_USERNAME=admin
|
||||
- TYPO3_SETUP_ADMIN_PASSWORD=Password:joh316
|
||||
- TYPO3_PROJECT_NAME=EXT:{{EXTENSION_KEY}} Dev Environment
|
||||
- TYPO3_SERVER_TYPE=apache
|
||||
volumes:
|
||||
- type: bind
|
||||
source: ../
|
||||
target: /var/www/{{EXTENSION_KEY}}
|
||||
consistency: cached
|
||||
- v11-data:/var/www/html/v11
|
||||
- v12-data:/var/www/html/v12
|
||||
- v13-data:/var/www/html/v13
|
||||
volumes:
|
||||
v11-data:
|
||||
name: "${DDEV_SITENAME}-v11-data"
|
||||
v12-data:
|
||||
name: "${DDEV_SITENAME}-v12-data"
|
||||
v13-data:
|
||||
name: "${DDEV_SITENAME}-v13-data"
|
||||
```
|
||||
|
||||
#### 3.3-3.7: Additional Configuration Files
|
||||
|
||||
Generate the following files using the templates from this skill repository:
|
||||
- `.ddev/apache/apache-site.conf` (replace `{{EXTENSION_KEY}}` and `{{DDEV_SITENAME}}`)
|
||||
- `.ddev/web-build/Dockerfile` (replace `{{EXTENSION_KEY}}` and `{{DDEV_SITENAME}}`)
|
||||
- `.ddev/commands/web/install-v11` (make executable)
|
||||
- `.ddev/commands/web/install-v12` (make executable)
|
||||
- `.ddev/commands/web/install-v13` (make executable)
|
||||
- `.ddev/commands/web/install-all` (make executable)
|
||||
- `.ddev/commands/host/docs` (make executable) - for rendering documentation
|
||||
|
||||
#### 3.8: .envrc (direnv Configuration)
|
||||
|
||||
Generate `.envrc` in the project root for direnv integration:
|
||||
|
||||
Replace these variables:
|
||||
- `{{EXTENSION_KEY}}` → Extension key with underscores (e.g., `my_ext`)
|
||||
- `{{DDEV_SITENAME}}` → DDEV sitename (e.g., `my-ext`)
|
||||
|
||||
```bash
|
||||
# direnv configuration for {{EXTENSION_KEY}}
|
||||
# Auto-generated by typo3-ddev-skill
|
||||
|
||||
# Add composer bin directory to PATH
|
||||
PATH_add vendor/bin
|
||||
|
||||
# Composer configuration
|
||||
export COMPOSER_PROCESS_TIMEOUT=600
|
||||
|
||||
# DDEV environment variables
|
||||
export DDEV_PROJECT={{DDEV_SITENAME}}
|
||||
export DDEV_PRIMARY_URL=https://{{DDEV_SITENAME}}.ddev.site
|
||||
export DDEV_DOCROOT_URL=https://docs.{{DDEV_SITENAME}}.ddev.site
|
||||
|
||||
# Load local environment overrides if present
|
||||
dotenv_if_exists .env.local
|
||||
|
||||
# Display activation message
|
||||
echo "✅ {{EXTENSION_KEY}} development environment activated"
|
||||
echo ""
|
||||
echo "🌐 DDEV Environment:"
|
||||
echo " Primary: https://{{DDEV_SITENAME}}.ddev.site"
|
||||
echo " Docs: https://docs.{{DDEV_SITENAME}}.ddev.site"
|
||||
echo " TYPO3 v11: https://v11.{{DDEV_SITENAME}}.ddev.site"
|
||||
echo " TYPO3 v12: https://v12.{{DDEV_SITENAME}}.ddev.site"
|
||||
echo " TYPO3 v13: https://v13.{{DDEV_SITENAME}}.ddev.site"
|
||||
echo ""
|
||||
echo "🚀 DDEV Commands:"
|
||||
echo " ddev start Start DDEV environment"
|
||||
echo " ddev stop Stop DDEV environment"
|
||||
echo " ddev restart Restart DDEV environment"
|
||||
echo " ddev install-v11 Install TYPO3 11.5 LTS"
|
||||
echo " ddev install-v12 Install TYPO3 12.4 LTS"
|
||||
echo " ddev install-v13 Install TYPO3 13.4 LTS"
|
||||
echo " ddev install-all Install all TYPO3 versions"
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
- Adds `vendor/bin` to PATH for Composer tools
|
||||
- Exports DDEV environment variables for easy access
|
||||
- Displays helpful information when entering the project directory
|
||||
- Lists available DDEV URLs and commands
|
||||
|
||||
**User action required:** Run `direnv allow` to activate the configuration.
|
||||
|
||||
**Optional:** If direnv is not installed, the file can be safely ignored.
|
||||
|
||||
### Step 4: Initialize DDEV
|
||||
|
||||
After generating all files, guide the user through starting DDEV:
|
||||
|
||||
```bash
|
||||
# Start DDEV containers
|
||||
ddev start
|
||||
```
|
||||
|
||||
Wait for DDEV to start successfully.
|
||||
|
||||
### Step 5: Install TYPO3 Environments
|
||||
|
||||
Provide options to user:
|
||||
|
||||
**Option A: Install All Versions** (Recommended for compatibility testing)
|
||||
```bash
|
||||
ddev install-all
|
||||
```
|
||||
|
||||
**Option B: Install Specific Version** (Faster for targeted development)
|
||||
```bash
|
||||
ddev install-v11 # TYPO3 11.5 LTS
|
||||
ddev install-v12 # TYPO3 12.4 LTS
|
||||
ddev install-v13 # TYPO3 13.4 LTS
|
||||
```
|
||||
|
||||
Installation time: ~2-5 minutes per version (depending on network speed)
|
||||
|
||||
**What gets installed automatically:**
|
||||
- ✅ TYPO3 Core (specified version)
|
||||
- ✅ Your extension (activated and ready to use)
|
||||
- ✅ TYPO3 Backend Styleguide (for UI pattern reference)
|
||||
- ✅ Extension Manager
|
||||
- ✅ Introduction Package (86+ pages with demo content for testing)
|
||||
|
||||
### Step 6: Provide Access Information
|
||||
|
||||
After successful installation, display:
|
||||
|
||||
```
|
||||
✅ DDEV Environment Ready!
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
📋 Overview Dashboard:
|
||||
https://{{DDEV_SITENAME}}.ddev.site/
|
||||
|
||||
🌐 TYPO3 Installations:
|
||||
|
||||
TYPO3 11.5 LTS
|
||||
Frontend: https://v11.{{DDEV_SITENAME}}.ddev.site/
|
||||
Backend: https://v11.{{DDEV_SITENAME}}.ddev.site/typo3/
|
||||
|
||||
TYPO3 12.4 LTS
|
||||
Frontend: https://v12.{{DDEV_SITENAME}}.ddev.site/
|
||||
Backend: https://v12.{{DDEV_SITENAME}}.ddev.site/typo3/
|
||||
|
||||
TYPO3 13.4 LTS
|
||||
Frontend: https://v13.{{DDEV_SITENAME}}.ddev.site/
|
||||
Backend: https://v13.{{DDEV_SITENAME}}.ddev.site/typo3/
|
||||
|
||||
🔑 Backend Credentials:
|
||||
Username: admin
|
||||
Password: Password:joh316
|
||||
|
||||
📦 Your Extension:
|
||||
Installed in all TYPO3 versions
|
||||
Source: /var/www/{{EXTENSION_KEY}}/ (bind-mounted from project root)
|
||||
|
||||
⚙️ Useful Commands:
|
||||
ddev restart # Restart containers
|
||||
ddev ssh # SSH into web container
|
||||
ddev logs # View container logs
|
||||
ddev describe # Show project details
|
||||
ddev stop # Stop containers
|
||||
ddev delete # Remove containers (keeps volumes)
|
||||
```
|
||||
|
||||
### Step 7: Generate Makefile (Optional but Recommended)
|
||||
|
||||
After DDEV setup, generate a standardized Makefile following Netresearch's pattern for convenient command access:
|
||||
|
||||
```bash
|
||||
ddev generate-makefile
|
||||
```
|
||||
|
||||
#### Makefile Features
|
||||
|
||||
The generated Makefile provides a unified interface with 30+ targets:
|
||||
|
||||
**DDEV Management:**
|
||||
```bash
|
||||
make up # Complete startup (start DDEV + install all TYPO3 versions)
|
||||
make start # Start DDEV environment
|
||||
make stop # Stop DDEV environment
|
||||
make install-v11 # Install TYPO3 11.5 LTS
|
||||
make install-v12 # Install TYPO3 12.4 LTS
|
||||
make install-v13 # Install TYPO3 13.4 LTS
|
||||
make install-all # Install all TYPO3 versions
|
||||
make urls # Show all access URLs
|
||||
```
|
||||
|
||||
**Testing & Quality:**
|
||||
```bash
|
||||
make test # Run all tests (unit + functional)
|
||||
make test-unit # Run unit tests only
|
||||
make test-functional # Run functional tests
|
||||
make lint # Run all linters (PHP syntax + PHPStan + code style)
|
||||
make format # Auto-fix code style issues
|
||||
make typecheck # Run PHPStan static analysis
|
||||
make ci # Complete CI pipeline (pre-commit checks)
|
||||
```
|
||||
|
||||
**Developer Convenience:**
|
||||
```bash
|
||||
make help # Show all available commands (default target)
|
||||
make ssh # SSH into DDEV web container
|
||||
make clean # Clean temporary files and caches
|
||||
```
|
||||
|
||||
**Extension-Specific Commands:**
|
||||
|
||||
The Makefile includes a customizable section for extension-specific commands. Developers can add their own targets following the pattern:
|
||||
|
||||
```makefile
|
||||
.PHONY: my-command
|
||||
my-command: ## Description of my command
|
||||
ddev ssh -d v12 "vendor/bin/typo3 extension:command"
|
||||
```
|
||||
|
||||
#### Why Use the Makefile?
|
||||
|
||||
1. **Consistency** - Follows Netresearch's established pattern across TYPO3 extensions
|
||||
2. **Single Command Setup** - `make up` does everything (DDEV start + install all versions)
|
||||
3. **Discoverability** - `make help` shows all available commands with descriptions
|
||||
4. **CI Integration** - Standardized `make ci`, `make test`, `make lint` for pipelines
|
||||
5. **Developer Experience** - Familiar interface, shorter commands than `ddev` equivalents
|
||||
6. **Documentation** - Self-documenting with auto-generated help
|
||||
|
||||
#### Example Workflow
|
||||
|
||||
```bash
|
||||
# Initial setup
|
||||
make up # Start DDEV + install all TYPO3 versions
|
||||
|
||||
# Development
|
||||
make lint # Check code quality
|
||||
make test # Run tests
|
||||
make format # Fix code style
|
||||
|
||||
# Check URLs
|
||||
make urls # Show all access URLs
|
||||
|
||||
# Complete CI check before commit
|
||||
make ci # Run full quality pipeline
|
||||
```
|
||||
|
||||
#### Customization
|
||||
|
||||
After generation, edit the Makefile to add project-specific commands while keeping core targets (up, start, test, lint, ci) unchanged for consistency across projects.
|
||||
|
||||
The Makefile is optional but highly recommended for improved developer experience.
|
||||
|
||||
### Step 8: Generate Project Index Page (Optional but Recommended)
|
||||
|
||||
After DDEV setup, generate a beautiful overview page for easy access to all TYPO3 versions and development tools:
|
||||
|
||||
```bash
|
||||
ddev generate-index
|
||||
```
|
||||
|
||||
#### Index Page Features
|
||||
|
||||
The generated `index.html` provides a centralized dashboard with:
|
||||
|
||||
**Visual Overview:**
|
||||
- Modern, responsive design with gradient background
|
||||
- Card-based layout for each TYPO3 version
|
||||
- Clear visual hierarchy and hover effects
|
||||
- Mobile-friendly responsive grid
|
||||
|
||||
**Quick Access Links:**
|
||||
```
|
||||
TYPO3 v12 LTS:
|
||||
- Frontend: https://v12.{project}.ddev.site/
|
||||
- Backend: https://v12.{project}.ddev.site/typo3/
|
||||
|
||||
TYPO3 v13 LTS:
|
||||
- Frontend: https://v13.{project}.ddev.site/
|
||||
- Backend: https://v13.{project}.ddev.site/typo3/
|
||||
```
|
||||
|
||||
**Development Tools:**
|
||||
- Backend credentials display (admin / Password:joh316)
|
||||
- Mailpit access link
|
||||
- Documentation link (if available)
|
||||
|
||||
#### Why Use the Index Page?
|
||||
|
||||
1. **Single Entry Point** - Access all TYPO3 versions from one beautiful page
|
||||
2. **Eliminates URL Guessing** - No need to remember subdomain patterns
|
||||
3. **Professional Presentation** - Polished interface for the development environment
|
||||
4. **Credential Reference** - Backend login info always visible
|
||||
5. **Tool Discovery** - Links to Mailpit and other development tools
|
||||
6. **Multi-Version Testing** - Easy switching between TYPO3 12 and 13
|
||||
|
||||
#### Example Usage
|
||||
|
||||
```bash
|
||||
# After DDEV setup
|
||||
ddev generate-index
|
||||
|
||||
# Access the overview page
|
||||
open https://{project}.ddev.site/
|
||||
# or
|
||||
open http://{project}.ddev.site/
|
||||
```
|
||||
|
||||
The index page is accessible at both the main domain (e.g., `temporal-cache.ddev.site`) and serves as the landing page for the entire development environment.
|
||||
|
||||
#### Customization
|
||||
|
||||
After generation, you can edit `index.html` to:
|
||||
- Add extension-specific documentation links
|
||||
- Include CI/CD status badges
|
||||
- Add custom tool links (PHPMyAdmin, Redis Commander, etc.)
|
||||
- Customize colors, branding, or layout
|
||||
- Add project-specific information or notes
|
||||
|
||||
The index page is optional but highly recommended for improved developer experience, especially in multi-version testing environments.
|
||||
|
||||
**For advanced branding and customization details including:**
|
||||
- Automatic branding detection (Netresearch/TYPO3/generic)
|
||||
- Brand-specific color schemes and typography
|
||||
- Design specifications
|
||||
|
||||
See: `references/index-page-generation.md`
|
||||
|
||||
## Error Handling
|
||||
|
||||
When encountering errors during setup or installation:
|
||||
|
||||
1. **Check error category**: Identify if the error relates to prerequisites, database setup, extension detection, or service configuration
|
||||
2. **Consult troubleshooting guide**: Read `references/troubleshooting.md` and locate the relevant error category
|
||||
3. **Apply recommended solution**: Follow the step-by-step resolution guide for the specific error
|
||||
4. **For Windows/WSL2 environments**: If error persists on Windows, consult `references/windows-fixes.md` for platform-specific solutions (CRLF issues, health check failures, Apache config problems)
|
||||
5. **Performance issues on Windows**: Consult `references/windows-optimizations.md` for performance tuning
|
||||
|
||||
Report the error to the user with the specific solution being applied, then re-attempt the failed operation.
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
When user requests customizations beyond the standard setup:
|
||||
|
||||
1. **Identify customization type**: Determine if request involves PHP version, database engine, debugging tools, TYPO3 versions, or additional services
|
||||
2. **Consult advanced options guide**: Read `references/advanced-options.md` for the relevant configuration section
|
||||
3. **Present options to user**: Show available choices (e.g., PHP 8.1/8.2/8.3, database engines with tiered selection logic)
|
||||
4. **Apply configuration**: Follow the templates and instructions in `references/advanced-options.md` to implement the requested customization
|
||||
5. **Configure optional services**: If user needs Valkey/Redis caching, MailPit, Ofelia scheduler, or other services, follow the service configuration templates in the advanced guide
|
||||
|
||||
Offer customization options proactively when user mentions performance requirements, specific PHP versions, or debugging needs.
|
||||
|
||||
## Documentation Rendering
|
||||
|
||||
### Rendering Extension Documentation
|
||||
|
||||
If the extension has a `Documentation/` directory with reStructuredText (.rst) files:
|
||||
|
||||
```bash
|
||||
ddev docs
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
- Renders `.rst` documentation files to HTML using TYPO3's official render-guides Docker image
|
||||
- Outputs to `Documentation-GENERATED-temp/`
|
||||
- Makes documentation accessible at `https://docs.{{DDEV_SITENAME}}.ddev.site/`
|
||||
|
||||
**Requirements:**
|
||||
- `Documentation/Index.rst` must exist
|
||||
- Docker must be running on the host
|
||||
|
||||
**Output:**
|
||||
```
|
||||
📚 Rendering TYPO3 Extension Documentation
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
📖 Source: Documentation/
|
||||
📦 Output: Documentation-GENERATED-temp/
|
||||
|
||||
🔨 Rendering documentation with TYPO3 render-guides...
|
||||
|
||||
✅ Documentation rendered successfully!
|
||||
|
||||
🌐 View at: https://docs.{{DDEV_SITENAME}}.ddev.site/
|
||||
```
|
||||
|
||||
**Note:** The docs subdomain is automatically configured in `.ddev/apache/apache-site.conf` and accessible after running `ddev docs`.
|
||||
|
||||
## Demo Content (Introduction Package)
|
||||
|
||||
When discussing testing capabilities with users, inform them that the Introduction Package is automatically installed with every TYPO3 version during setup, providing:
|
||||
|
||||
- 86+ pages with full page tree structure for testing navigation
|
||||
- 226+ content elements (text, images, forms, tables) for testing rendering
|
||||
- Multi-language support (English, German, Danish) for testing translations
|
||||
- Bootstrap Package responsive theme for testing frontend integration
|
||||
- RTE feature examples for testing rich text functionality
|
||||
|
||||
Direct users to test their extension against this demo content at:
|
||||
- Frontend: `https://v11.{{DDEV_SITENAME}}.ddev.site/` (or v12, v13)
|
||||
- Backend: `https://v11.{{DDEV_SITENAME}}.ddev.site/typo3/` (or v12, v13)
|
||||
|
||||
No manual installation required—the package installs automatically during `ddev install-all` or individual version commands.
|
||||
|
||||
## Extension Auto-Configuration
|
||||
|
||||
When user's extension requires additional setup beyond standard installation (RTE configuration, Page TSConfig, TypoScript), guide them through creating a custom configuration command:
|
||||
|
||||
```bash
|
||||
# Copy the configure-extension template
|
||||
cp .ddev/templates/commands/web/configure-extension.optional .ddev/commands/web/configure-{{EXTENSION_KEY}}
|
||||
chmod +x .ddev/commands/web/configure-{{EXTENSION_KEY}}
|
||||
|
||||
# Edit to add your extension-specific configuration
|
||||
# Examples: RTE YAML, Page TSConfig, site package setup
|
||||
|
||||
# Run after TYPO3installation
|
||||
ddev configure-{{EXTENSION_KEY}} v13
|
||||
```
|
||||
|
||||
### Identify Configuration Needs by Extension Type
|
||||
|
||||
**For RTE/CKEditor Extensions**, configure:
|
||||
- RTE YAML configuration importing the plugin
|
||||
- Toolbar buttons and editor config
|
||||
- Page TSConfig for RTE presets
|
||||
- Example reference: `rte_ckeditor_image` with custom image handling
|
||||
|
||||
**For Backend Module Extensions**, configure:
|
||||
- Page TSConfig for module access
|
||||
- User permissions setup
|
||||
- Initial database records
|
||||
|
||||
**For Frontend Plugin Extensions**, configure:
|
||||
- TypoScript configuration
|
||||
- Example content elements
|
||||
- Plugin settings
|
||||
|
||||
### Template Structure
|
||||
|
||||
The `configure-extension.optional` template includes:
|
||||
|
||||
1. **Introduction Package Installation** - Demo content for testing
|
||||
2. **Site Package Creation** - Programmatic extension setup with:
|
||||
- `ext_emconf.php` - Extension metadata
|
||||
- `ext_localconf.php` - Global configuration
|
||||
- `ext_tables.php` - Table definitions and TSConfig loading
|
||||
3. **Configuration Files** - RTE YAML, Page TSConfig, TypoScript
|
||||
4. **Cache Flushing** - Ensure changes take effect
|
||||
5. **Success Message** - Clear next steps for developer
|
||||
|
||||
### TYPO3 v13 Site Sets Approach
|
||||
|
||||
**Important:** TYPO3 v13 introduces site sets as the modern approach for TypoScript and configuration loading, replacing static templates.
|
||||
|
||||
**Site Sets vs Static Templates:**
|
||||
|
||||
| Feature | Site Sets (TYPO3 v13+) | Static Templates (Legacy) |
|
||||
|---------|------------------------|---------------------------|
|
||||
| Configuration | `config.yaml` in site config | `sys_template` database records |
|
||||
| Loading Order | Dependency-based | Include order in template |
|
||||
| Type Safety | Schema-validated | No validation |
|
||||
| Maintenance | Version-controlled files | Database-stored |
|
||||
| Best Practice | ✅ Use for TYPO3 v13+ | ⚠️ Legacy approach |
|
||||
|
||||
**How Extensions Should Provide Configuration:**
|
||||
|
||||
1. **Create a Site Set** in your extension:
|
||||
```
|
||||
Configuration/Sets/YourExtensionName/
|
||||
├── config.yaml # Site set metadata
|
||||
├── setup.typoscript # TypoScript configuration
|
||||
└── page.tsconfig # Page TSConfig (optional)
|
||||
```
|
||||
|
||||
2. **Define Site Set** in `config.yaml`:
|
||||
```yaml
|
||||
name: vendor/extension-name
|
||||
label: 'Your Extension Name'
|
||||
# Optional dependencies load BEFORE your set
|
||||
optionalDependencies:
|
||||
- typo3/fluid-styled-content
|
||||
- bk2k/bootstrap-package
|
||||
```
|
||||
|
||||
3. **Import TypoScript** in `setup.typoscript`:
|
||||
```typoscript
|
||||
@import 'EXT:your_extension/Configuration/TypoScript/*.typoscript'
|
||||
```
|
||||
|
||||
4. **Users Add to Site Configuration** (`config/sites/main/config.yaml`):
|
||||
```yaml
|
||||
dependencies:
|
||||
- bootstrap-package/full # Frontend rendering
|
||||
- vendor/extension-name # Your extension
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- ✅ No sys_template database records needed
|
||||
- ✅ Proper dependency ordering
|
||||
- ✅ Version-controlled configuration
|
||||
- ✅ No static template conflicts
|
||||
- ✅ Type-safe settings with schema
|
||||
|
||||
**Common Pitfall:** Avoid loading TypoScript via BOTH site sets AND static templates - this causes double-loading and configuration conflicts (e.g., duplicate `lib.parseFunc_RTE.tags.img` processors causing unexpected behavior).
|
||||
|
||||
**Debugging Site Sets:**
|
||||
```bash
|
||||
# Check active site sets for a site
|
||||
ddev exec vendor/bin/typo3 site:show <siteIdentifier>
|
||||
|
||||
# View resolved TypoScript (TYPO3 v13)
|
||||
Backend → Site Management → Sites → [Your Site] → Dependencies
|
||||
```
|
||||
|
||||
### Example: RTE Configuration Command
|
||||
|
||||
For a CKEditor plugin extension:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
VERSION=${1:-v13}
|
||||
INSTALL_DIR=/var/www/html/$VERSION
|
||||
|
||||
# Install demo content
|
||||
composer require typo3/cms-introduction -d $INSTALL_DIR
|
||||
vendor/bin/typo3 extension:setup --extension=introduction
|
||||
|
||||
# Create site package
|
||||
mkdir -p $INSTALL_DIR/public/typo3conf/ext/site_rte/Configuration/RTE
|
||||
|
||||
# Create RTE YAML importing your plugin
|
||||
cat > .../Configuration/RTE/Default.yaml << 'EOF'
|
||||
imports:
|
||||
- { resource: "EXT:rte_ckeditor/Configuration/RTE/Default.yaml" }
|
||||
- { resource: "EXT:your_extension/Configuration/RTE/Plugin.yaml" }
|
||||
|
||||
editor:
|
||||
config:
|
||||
toolbar:
|
||||
items:
|
||||
- your_custom_button
|
||||
- ...
|
||||
EOF
|
||||
|
||||
# Flush caches
|
||||
vendor/bin/typo3 cache:flush
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- One-command setup after TYPO3 installation
|
||||
- Consistent configuration across team
|
||||
- Demo content ready for testing
|
||||
- Reduces manual configuration errors
|
||||
|
||||
### Step 9: Verify Installation
|
||||
|
||||
After installation completes, perform these verification steps in order:
|
||||
|
||||
1. **Confirm prerequisites passed**: Review output from Step 1 prerequisite validation
|
||||
2. **Verify extension metadata**: Check that extracted values match project configuration
|
||||
3. **Test DDEV status**: Run `ddev describe` to confirm containers are running
|
||||
4. **Validate TYPO3 installations**: Confirm at least one version installed without errors
|
||||
5. **Test URL accessibility**: Open each TYPO3 version URL in browser to verify routing
|
||||
6. **Verify backend access**: Log in to TYPO3 backend with credentials (admin / Password:joh316)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Database Already Exists Error
|
||||
|
||||
If reinstalling TYPO3 and you get "database already exists" errors:
|
||||
|
||||
```bash
|
||||
# Clean up and recreate database
|
||||
ddev mysql -e "DROP DATABASE IF EXISTS v13; CREATE DATABASE v13;"
|
||||
|
||||
# Now retry installation
|
||||
ddev install-v13
|
||||
```
|
||||
|
||||
### TYPO3 Setup Shows "Database contains tables" Error
|
||||
|
||||
```bash
|
||||
# Option 1: Clean database
|
||||
ddev mysql v13 -e "DROP DATABASE v13; CREATE DATABASE v13;"
|
||||
|
||||
# Option 2: Use different database name
|
||||
# Edit install-v* script to use different DB name
|
||||
```
|
||||
|
||||
### Services Not Loading
|
||||
|
||||
If Redis, MailPit, or Ofelia containers don't start:
|
||||
|
||||
```bash
|
||||
# Check container status
|
||||
docker ps --filter "name=ddev-{{DDEV_SITENAME}}"
|
||||
|
||||
# View logs
|
||||
docker logs ddev-{{DDEV_SITENAME}}-redis
|
||||
docker logs ddev-{{DDEV_SITENAME}}-mailpit
|
||||
docker logs ddev-{{DDEV_SITENAME}}-ofelia
|
||||
|
||||
# Restart DDEV
|
||||
ddev restart
|
||||
```
|
||||
|
||||
### Extension Not Appearing in Backend
|
||||
|
||||
```bash
|
||||
# Flush all caches
|
||||
ddev exec -d /var/www/html/v13 vendor/bin/typo3 cache:flush
|
||||
|
||||
# Check extension is symlinked
|
||||
ddev exec ls -la /var/www/html/v13/vendor/{{VENDOR}}/{{EXTENSION_KEY}}
|
||||
```
|
||||
|
||||
### .gitignore Configuration Best Practices
|
||||
|
||||
**CRITICAL:** Ensure `.ddev/.gitignore` is committed to version control, NOT ignored!
|
||||
|
||||
**The Double-Ignore Anti-Pattern:**
|
||||
|
||||
DDEV often generates `.ddev/.gitignore` with this problematic structure:
|
||||
|
||||
```
|
||||
#ddev-generated: Automatically generated ddev .gitignore.
|
||||
# You can remove the above line if you want to edit and maintain this file yourself.
|
||||
/.gitignore ← File ignores ITSELF!
|
||||
```
|
||||
|
||||
**The Problem:**
|
||||
|
||||
1. `.ddev/.gitignore` ignores itself (`/.gitignore`)
|
||||
2. Root `.gitignore` also ignores it (`.ddev/.gitignore`)
|
||||
3. Result: DDEV ignore rules NOT shared with team
|
||||
4. Each developer must generate their own DDEV config
|
||||
5. Inconsistent git behavior across team members
|
||||
|
||||
**The Fix:**
|
||||
|
||||
1. **Remove from root `.gitignore`:**
|
||||
```diff
|
||||
# .gitignore (ROOT)
|
||||
.ddev/.homeadditions
|
||||
.ddev/.ddev-docker-compose-full.yaml
|
||||
- .ddev/.gitignore ← REMOVE THIS LINE
|
||||
```
|
||||
|
||||
2. **Fix `.ddev/.gitignore` to not self-ignore:**
|
||||
```diff
|
||||
# .ddev/.gitignore (UPDATED)
|
||||
- #ddev-generated: Automatically generated ddev .gitignore.
|
||||
- # You can remove the above line if you want to edit and maintain this file yourself.
|
||||
- /.gitignore
|
||||
+ # DDEV gitignore rules
|
||||
+ # Manually maintained to ensure consistency across team
|
||||
|
||||
/**/*.example
|
||||
/.dbimageBuild
|
||||
/.ddev-docker-*.yaml
|
||||
```
|
||||
|
||||
3. **Commit DDEV configuration to share with team:**
|
||||
```bash
|
||||
git add .ddev/.gitignore .ddev/config.yaml .ddev/docker-compose.*.yaml
|
||||
git add .ddev/apache/ .ddev/commands/ .ddev/web-build/
|
||||
git commit -m "[TASK] Add DDEV configuration for development environment"
|
||||
```
|
||||
|
||||
**What to Commit vs. Ignore:**
|
||||
|
||||
✅ **Commit (share with team):**
|
||||
- `.ddev/.gitignore` (ignore rules)
|
||||
- `.ddev/config.yaml` (main config)
|
||||
- `.ddev/docker-compose.*.yaml` (custom services)
|
||||
- `.ddev/apache/` (custom Apache config)
|
||||
- `.ddev/commands/` (custom DDEV commands)
|
||||
- `.ddev/web-build/` (custom Dockerfile)
|
||||
|
||||
❌ **Ignore (personal/generated):**
|
||||
- `.ddev/.homeadditions` (personal shell config)
|
||||
- `.ddev/.ddev-docker-compose-full.yaml` (auto-generated)
|
||||
- `.ddev/db_snapshots/` (database snapshots)
|
||||
- `.ddev/.sshimageBuild` (build artifacts)
|
||||
- `.ddev/.webimageBuild` (build artifacts)
|
||||
|
||||
**Validation:**
|
||||
|
||||
After fixing, verify:
|
||||
```bash
|
||||
# Should show .ddev/.gitignore as tracked
|
||||
git ls-files .ddev/.gitignore
|
||||
|
||||
# Should list DDEV files to be committed
|
||||
git status .ddev/
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
|
||||
- ✅ Consistent environment across all developers
|
||||
- ✅ Shared DDEV ignore rules
|
||||
- ✅ Quick onboarding: `git clone && ddev start`
|
||||
- ✅ No manual DDEV configuration needed
|
||||
- ✅ Team follows same git patterns
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
Before completing, verify:
|
||||
|
||||
1. **Confirm prerequisites passed**: Review output from Step 1 prerequisite validation
|
||||
2. **Verify extension metadata**: Check that extracted values match project configuration
|
||||
3. **Test DDEV status**: Run `ddev describe` to confirm containers are running
|
||||
4. **Validate TYPO3 installations**: Confirm at least one version installed without errors
|
||||
5. **Test URL accessibility**: Open each TYPO3 version URL in browser to verify routing
|
||||
6. **Verify backend access**: Log in to TYPO3 backend with credentials (admin / Password:joh316)
|
||||
7. **Confirm extension activation**: Check Extension Manager to ensure extension is loaded
|
||||
|
||||
If any verification fails, consult the Troubleshooting section above for resolution steps.
|
||||
|
||||
## Output Formatting Guidelines
|
||||
|
||||
Throughout all steps, format output for clarity:
|
||||
- Use emojis for visual indicators: ✅ (success), ❌ (error), 🔧 (configuration), 📦 (installation), 🌐 (URLs), 🔑 (credentials)
|
||||
- Display progress indicators during operations taking >5 seconds
|
||||
- Provide copy-pasteable command blocks with explanations
|
||||
- Present configuration values in formatted tables or code blocks
|
||||
- Explain what each command does before executing it
|
||||
44
skills/typo3-ddev/assets/templates/.envrc
Normal file
44
skills/typo3-ddev/assets/templates/.envrc
Normal file
@@ -0,0 +1,44 @@
|
||||
# direnv configuration for {{EXTENSION_KEY}}
|
||||
# Auto-generated by typo3-ddev-skill
|
||||
#
|
||||
# Usage: Run `direnv allow` to activate this configuration
|
||||
# Install direnv: https://direnv.net/
|
||||
|
||||
# Add composer bin directory to PATH
|
||||
PATH_add vendor/bin
|
||||
|
||||
# Composer configuration
|
||||
export COMPOSER_PROCESS_TIMEOUT=600
|
||||
|
||||
# DDEV environment variables
|
||||
export DDEV_PROJECT={{DDEV_SITENAME}}
|
||||
export DDEV_PRIMARY_URL=https://{{DDEV_SITENAME}}.ddev.site
|
||||
export DDEV_DOCROOT_URL=https://docs.{{DDEV_SITENAME}}.ddev.site
|
||||
|
||||
# Load local environment overrides if present
|
||||
dotenv_if_exists .env.local
|
||||
|
||||
# Display activation message
|
||||
echo "✅ {{EXTENSION_KEY}} development environment activated"
|
||||
echo ""
|
||||
echo "🌐 DDEV Environment:"
|
||||
echo " Primary: https://{{DDEV_SITENAME}}.ddev.site"
|
||||
echo " Docs: https://docs.{{DDEV_SITENAME}}.ddev.site"
|
||||
echo " TYPO3 v11: https://v11.{{DDEV_SITENAME}}.ddev.site"
|
||||
echo " TYPO3 v12: https://v12.{{DDEV_SITENAME}}.ddev.site"
|
||||
echo " TYPO3 v13: https://v13.{{DDEV_SITENAME}}.ddev.site"
|
||||
echo ""
|
||||
echo "🚀 Quick Start:"
|
||||
echo " make up Complete startup (start + setup) ⭐"
|
||||
echo " make start Start DDEV environment"
|
||||
echo " make setup Complete setup (docs + install + configure)"
|
||||
echo ""
|
||||
echo "📚 DDEV Commands:"
|
||||
echo " ddev docs Render extension documentation"
|
||||
echo " ddev install-v11 Install TYPO3 11.5 LTS"
|
||||
echo " ddev install-v12 Install TYPO3 12.4 LTS"
|
||||
echo " ddev install-v13 Install TYPO3 13.4 LTS"
|
||||
echo " ddev install-all Install all TYPO3 versions"
|
||||
echo ""
|
||||
echo "📦 Composer Tools: Available in vendor/bin/"
|
||||
echo "🔧 More Commands: make help (if Makefile present)"
|
||||
124
skills/typo3-ddev/assets/templates/Makefile.template
Normal file
124
skills/typo3-ddev/assets/templates/Makefile.template
Normal file
@@ -0,0 +1,124 @@
|
||||
# Makefile for {{EXTENSION_NAME}} TYPO3 Extension
|
||||
# Auto-generated by ddev setup
|
||||
|
||||
.PHONY: help
|
||||
help: ## Show available targets
|
||||
@awk 'BEGIN{FS=":.*##";print "\nUsage: make <target>\n"} /^[a-zA-Z0-9_.-]+:.*##/ {printf " %-22s %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
||||
|
||||
# ===================================
|
||||
# DDEV Environment Commands
|
||||
# ===================================
|
||||
|
||||
.PHONY: up
|
||||
up: start setup ## Complete startup (start DDEV + run setup) - ONE COMMAND TO RULE THEM ALL
|
||||
|
||||
.PHONY: start
|
||||
start: ## Start DDEV environment
|
||||
ddev start
|
||||
|
||||
.PHONY: stop
|
||||
stop: ## Stop DDEV environment
|
||||
ddev stop
|
||||
|
||||
.PHONY: setup
|
||||
setup: ## Complete setup (install all TYPO3 versions)
|
||||
@ddev describe >/dev/null 2>&1 || ddev start
|
||||
ddev install-all
|
||||
|
||||
.PHONY: install-v12
|
||||
install-v12: ## Install TYPO3 v12.4 LTS with extension
|
||||
ddev install-v12
|
||||
|
||||
.PHONY: install-v13
|
||||
install-v13: ## Install TYPO3 v13.0 LTS with extension
|
||||
ddev install-v13
|
||||
|
||||
.PHONY: install-all
|
||||
install-all: ## Install all TYPO3 versions
|
||||
ddev install-all
|
||||
|
||||
.PHONY: ddev-restart
|
||||
ddev-restart: ## Restart DDEV containers
|
||||
ddev restart
|
||||
|
||||
.PHONY: ssh
|
||||
ssh: ## SSH into DDEV web container
|
||||
ddev ssh
|
||||
|
||||
# ===================================
|
||||
# Composer & Quality Commands
|
||||
# ===================================
|
||||
|
||||
.PHONY: install
|
||||
install: ## Install composer dependencies
|
||||
composer install
|
||||
|
||||
.PHONY: lint
|
||||
lint: ## Run all linters (PHP syntax + PHPStan + code style)
|
||||
@echo "==> Running PHP lint..."
|
||||
composer ci:test:php:lint || composer lint
|
||||
@echo "==> Running PHPStan..."
|
||||
composer phpstan || composer ci:phpstan || echo "⚠️ PHPStan not configured"
|
||||
@echo "==> Running code style check..."
|
||||
composer ci:test:php:cgl || composer cs:check || echo "⚠️ Code style check not configured"
|
||||
@echo "✅ All linters passed"
|
||||
|
||||
.PHONY: format
|
||||
format: ## Auto-fix code style issues (PSR-12)
|
||||
composer code:fix || composer cs:fix
|
||||
|
||||
.PHONY: typecheck
|
||||
typecheck: ## Run PHPStan static analysis
|
||||
composer phpstan || composer ci:phpstan
|
||||
|
||||
.PHONY: test
|
||||
test: ## Run all tests (unit + functional)
|
||||
composer test
|
||||
|
||||
.PHONY: test-unit
|
||||
test-unit: ## Run unit tests only
|
||||
composer test:unit
|
||||
|
||||
.PHONY: test-functional
|
||||
test-functional: ## Run functional tests
|
||||
composer test:functional
|
||||
|
||||
.PHONY: test-coverage
|
||||
test-coverage: ## Generate test coverage report
|
||||
composer test:coverage
|
||||
|
||||
.PHONY: ci
|
||||
ci: ## Run complete CI pipeline (pre-commit checks)
|
||||
composer ci || composer ci:test
|
||||
|
||||
.PHONY: clean
|
||||
clean: ## Clean temporary files and caches
|
||||
rm -rf .php-cs-fixer.cache
|
||||
rm -rf var/
|
||||
rm -rf .Build/.cache
|
||||
|
||||
# ===================================
|
||||
# Extension-Specific Commands
|
||||
# (Customize based on your extension)
|
||||
# ===================================
|
||||
|
||||
.PHONY: urls
|
||||
urls: ## Show all access URLs
|
||||
@echo ""
|
||||
@echo "{{EXTENSION_NAME}} - Access URLs"
|
||||
@echo "==============================="
|
||||
@echo ""
|
||||
@echo "TYPO3 v12.4 LTS:"
|
||||
@echo " Frontend: https://v12.{{DDEV_PROJECT}}.ddev.site/"
|
||||
@echo " Backend: https://v12.{{DDEV_PROJECT}}.ddev.site/typo3/"
|
||||
@echo ""
|
||||
@echo "TYPO3 v13.0 LTS:"
|
||||
@echo " Frontend: https://v13.{{DDEV_PROJECT}}.ddev.site/"
|
||||
@echo " Backend: https://v13.{{DDEV_PROJECT}}.ddev.site/typo3/"
|
||||
@echo ""
|
||||
@echo "Backend Credentials:"
|
||||
@echo " Username: admin"
|
||||
@echo " Password: Password:joh316"
|
||||
@echo ""
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
361
skills/typo3-ddev/assets/templates/README-SERVICES.md.optional
Normal file
361
skills/typo3-ddev/assets/templates/README-SERVICES.md.optional
Normal file
@@ -0,0 +1,361 @@
|
||||
# DDEV Services Documentation
|
||||
|
||||
This DDEV setup includes additional services for TYPO3 extension development.
|
||||
|
||||
## Included Services
|
||||
|
||||
### 1. Redis (Caching)
|
||||
|
||||
**Container**: `ddev-rte-ckeditor-image-redis`
|
||||
**Image**: `redis:7-alpine`
|
||||
**Port**: 6379 (internal)
|
||||
|
||||
**Purpose**: High-performance caching for TYPO3
|
||||
|
||||
**Access**:
|
||||
```bash
|
||||
# From host
|
||||
ddev redis-cli
|
||||
|
||||
# From web container
|
||||
ddev ssh
|
||||
redis-cli -h redis
|
||||
```
|
||||
|
||||
**Configuration**:
|
||||
- See `.ddev/config.redis.yaml` for TYPO3 configuration example
|
||||
- Add to `/var/www/html/v13/config/system/additional.php`
|
||||
|
||||
**Testing**:
|
||||
```bash
|
||||
ddev ssh
|
||||
redis-cli -h redis ping
|
||||
# Should return: PONG
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. MailPit (Email Testing)
|
||||
|
||||
**Container**: `ddev-{{DDEV_SITENAME}}-mailpit`
|
||||
**Image**: `axllent/mailpit`
|
||||
**Ports**:
|
||||
- 1025 (SMTP)
|
||||
- 8025 (Web UI)
|
||||
|
||||
**Purpose**: Catch all emails sent by TYPO3 for testing
|
||||
|
||||
**Access**:
|
||||
- **Web UI**: `http://rte-ckeditor-image.ddev.site:8025`
|
||||
- **SMTP**: `mailpit:1025` (from containers)
|
||||
|
||||
**TYPO3 Configuration**:
|
||||
Already configured in `.ddev/docker-compose.web.yaml`:
|
||||
```yaml
|
||||
TYPO3_INSTALL_MAIL_TRANSPORT: smtp
|
||||
TYPO3_INSTALL_MAIL_TRANSPORT_SMTP_SERVER: mailpit:1025
|
||||
```
|
||||
|
||||
Or manually in `AdditionalConfiguration.php`:
|
||||
```php
|
||||
$GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport'] = 'smtp';
|
||||
$GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport_smtp_server'] = 'mailpit:1025';
|
||||
```
|
||||
|
||||
**Testing**:
|
||||
```bash
|
||||
# Send test email from TYPO3
|
||||
ddev ssh
|
||||
cd /var/www/html/v13
|
||||
vendor/bin/typo3 mailer:spool:send
|
||||
|
||||
# View in MailPit UI
|
||||
open http://rte-ckeditor-image.ddev.site:8025
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Ofelia (Cron/Scheduler)
|
||||
|
||||
**Container**: `ddev-rte-ckeditor-image-ofelia`
|
||||
**Image**: `ghcr.io/netresearch/ofelia:latest`
|
||||
|
||||
**Purpose**: Run TYPO3 scheduler tasks automatically
|
||||
|
||||
**Configuration**: `.ddev/docker-compose.ofelia.yaml`
|
||||
|
||||
**Scheduled Jobs**:
|
||||
- TYPO3 scheduler for v11, v12, v13: Every 1 minute
|
||||
- Cache warmup for v13: Every 1 hour
|
||||
|
||||
**View Logs**:
|
||||
```bash
|
||||
# Check if Ofelia is running
|
||||
docker ps | grep ofelia
|
||||
|
||||
# View Ofelia logs
|
||||
docker logs -f ddev-rte-ckeditor-image-ofelia
|
||||
|
||||
# Check scheduler execution
|
||||
ddev ssh
|
||||
cd /var/www/html/v13
|
||||
vendor/bin/typo3 scheduler:list
|
||||
```
|
||||
|
||||
**Manual Execution**:
|
||||
```bash
|
||||
ddev ssh
|
||||
t3-scheduler-v13 # alias for scheduler:run on v13
|
||||
t3-scheduler-all # run scheduler on all versions
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Alternative: Traditional Cron in Web Container
|
||||
|
||||
If you prefer traditional cron instead of Ofelia:
|
||||
|
||||
1. **Enable cron in Dockerfile**:
|
||||
|
||||
Edit `.ddev/web-build/Dockerfile` and add:
|
||||
```dockerfile
|
||||
RUN apt-get update && apt-get install -y cron
|
||||
COPY install-cron.sh /opt/install-cron.sh
|
||||
RUN chmod +x /opt/install-cron.sh && /opt/install-cron.sh
|
||||
```
|
||||
|
||||
2. **Restart DDEV**:
|
||||
```bash
|
||||
ddev restart
|
||||
```
|
||||
|
||||
3. **Verify cron**:
|
||||
```bash
|
||||
ddev ssh
|
||||
crontab -l
|
||||
service cron status
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Service Management
|
||||
|
||||
### Start/Stop Services
|
||||
|
||||
```bash
|
||||
# Restart all services
|
||||
ddev restart
|
||||
|
||||
# Stop DDEV (keeps volumes)
|
||||
ddev stop
|
||||
|
||||
# Remove containers (keeps volumes)
|
||||
ddev delete
|
||||
|
||||
# Remove everything including volumes
|
||||
ddev delete --omit-snapshot --yes
|
||||
docker volume rm rte-ckeditor-image-redis-data
|
||||
```
|
||||
|
||||
### View Service Status
|
||||
|
||||
```bash
|
||||
# All DDEV containers
|
||||
ddev describe
|
||||
|
||||
# All containers
|
||||
docker ps | grep rte-ckeditor-image
|
||||
|
||||
# Service logs
|
||||
docker logs ddev-rte-ckeditor-image-redis
|
||||
docker logs ddev-{{DDEV_SITENAME}}-mailpit
|
||||
docker logs ddev-rte-ckeditor-image-ofelia
|
||||
```
|
||||
|
||||
### Access Services
|
||||
|
||||
```bash
|
||||
# Redis CLI
|
||||
ddev redis-cli
|
||||
|
||||
# Or from web container
|
||||
ddev ssh
|
||||
redis-cli -h redis
|
||||
|
||||
# MailPit web interface
|
||||
open http://rte-ckeditor-image.ddev.site:8025
|
||||
|
||||
# Check Redis connection from TYPO3
|
||||
ddev ssh
|
||||
cd /var/www/html/v13
|
||||
vendor/bin/typo3 cache:flush
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## TYPO3 Scheduler Configuration
|
||||
|
||||
### Enable Scheduler Tasks
|
||||
|
||||
1. **Access TYPO3 Backend**: https://v13.rte-ckeditor-image.ddev.site/typo3/
|
||||
2. **Login**: admin / Password:joh316
|
||||
3. **System → Scheduler**
|
||||
4. **Create tasks** (examples):
|
||||
- Table garbage collection
|
||||
- Index queue worker
|
||||
- File abstraction layer indexing
|
||||
- Import/Export tasks
|
||||
|
||||
### Verify Scheduler is Running
|
||||
|
||||
```bash
|
||||
ddev ssh
|
||||
cd /var/www/html/v13
|
||||
|
||||
# List all scheduler tasks
|
||||
vendor/bin/typo3 scheduler:list
|
||||
|
||||
# Run manually (for testing)
|
||||
vendor/bin/typo3 scheduler:run
|
||||
|
||||
# Check last execution time
|
||||
vendor/bin/typo3 scheduler:list --verbose
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Tuning
|
||||
|
||||
### Redis
|
||||
|
||||
**Memory Limit**: Currently 256MB
|
||||
**Eviction Policy**: `allkeys-lru` (Least Recently Used)
|
||||
|
||||
To adjust:
|
||||
```yaml
|
||||
# .ddev/docker-compose.services.yaml
|
||||
environment:
|
||||
- REDIS_MAXMEMORY=512mb # Increase if needed
|
||||
```
|
||||
|
||||
### MailPit
|
||||
|
||||
No tuning needed for development. All emails are stored in memory.
|
||||
|
||||
### Ofelia/Cron
|
||||
|
||||
**Frequency**: Default is every 1 minute
|
||||
To adjust, edit `.ddev/docker-compose.ofelia.yaml`:
|
||||
|
||||
```yaml
|
||||
# Every 5 minutes instead
|
||||
ofelia.job-exec.typo3-scheduler-v13.schedule: "@every 5m"
|
||||
|
||||
# Specific time (e.g., 2am daily)
|
||||
ofelia.job-exec.cache-warmup.schedule: "0 2 * * *"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Redis Not Connecting
|
||||
|
||||
```bash
|
||||
# Test Redis
|
||||
ddev ssh
|
||||
redis-cli -h redis ping
|
||||
|
||||
# Should return: PONG
|
||||
# If not, check Redis container
|
||||
docker logs ddev-rte-ckeditor-image-redis
|
||||
```
|
||||
|
||||
### MailPit Not Receiving Emails
|
||||
|
||||
```bash
|
||||
# Check TYPO3 mail configuration
|
||||
ddev ssh
|
||||
cd /var/www/html/v13
|
||||
vendor/bin/typo3 configuration:show MAIL
|
||||
|
||||
# Test email sending
|
||||
vendor/bin/typo3 mailer:spool:send
|
||||
```
|
||||
|
||||
### Scheduler Not Running
|
||||
|
||||
```bash
|
||||
# Check Ofelia logs
|
||||
docker logs -f ddev-rte-ckeditor-image-ofelia
|
||||
|
||||
# Manually run scheduler
|
||||
ddev ssh
|
||||
t3-scheduler-v13
|
||||
|
||||
# Check for errors
|
||||
cd /var/www/html/v13
|
||||
vendor/bin/typo3 scheduler:list --verbose
|
||||
```
|
||||
|
||||
### Remove Service
|
||||
|
||||
To remove a service, comment it out in `.ddev/docker-compose.services.yaml` and restart:
|
||||
|
||||
```bash
|
||||
ddev restart
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Additional Services (Optional)
|
||||
|
||||
### Adminer (Database GUI)
|
||||
|
||||
```bash
|
||||
ddev get ddev/ddev-adminer
|
||||
ddev restart
|
||||
# Access: https://rte-ckeditor-image.ddev.site:9999
|
||||
```
|
||||
|
||||
### Elasticsearch
|
||||
|
||||
```yaml
|
||||
# Add to .ddev/docker-compose.services.yaml
|
||||
elasticsearch:
|
||||
image: elasticsearch:8.10.2
|
||||
environment:
|
||||
- discovery.type=single-node
|
||||
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
|
||||
ports:
|
||||
- "9200"
|
||||
```
|
||||
|
||||
### Solr
|
||||
|
||||
```yaml
|
||||
# Add to .ddev/docker-compose.services.yaml
|
||||
solr:
|
||||
image: solr:9
|
||||
ports:
|
||||
- "8983"
|
||||
volumes:
|
||||
- solr-data:/var/solr
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Service | Access | Purpose |
|
||||
|---------|--------|---------|
|
||||
| Redis | `redis-cli -h redis` | Caching |
|
||||
| MailPit UI | http://localhost:8025 | Email testing |
|
||||
| MailPit SMTP | `mailpit:1025` | Email delivery |
|
||||
| Ofelia | Background | Cron jobs |
|
||||
| Web | https://*.ddev.site | TYPO3 instances |
|
||||
| Database | `ddev mysql` | MariaDB |
|
||||
|
||||
---
|
||||
|
||||
**Questions?** Check DDEV docs: https://ddev.readthedocs.io/
|
||||
161
skills/typo3-ddev/assets/templates/apache/apache-site.conf
Normal file
161
skills/typo3-ddev/assets/templates/apache/apache-site.conf
Normal file
@@ -0,0 +1,161 @@
|
||||
ServerName {{DDEV_SITENAME}}.ddev.site
|
||||
|
||||
<VirtualHost *:80>
|
||||
# Workaround from https://mail-archives.apache.org/mod_mbox/httpd-users/201403.mbox/%3C49404A24C7FAD94BB7B45E86A9305F6214D04652@MSGEXSV21103.ent.wfb.bank.corp%3E
|
||||
# See also https://gist.github.com/nurtext/b6ac07ac7d8c372bc8eb
|
||||
|
||||
RewriteEngine On
|
||||
RewriteCond %{HTTP:X-Forwarded-Proto} =https
|
||||
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d
|
||||
RewriteRule ^(.+[^/])$ https://%{HTTP_HOST}$1/ [redirect,last]
|
||||
|
||||
SetEnvIf X-Forwarded-Proto "https" HTTPS=on
|
||||
|
||||
ServerAdmin webmaster@localhost
|
||||
DocumentRoot /var/www/html
|
||||
<Directory "/var/www/html/">
|
||||
AllowOverride All
|
||||
Allow from All
|
||||
</Directory>
|
||||
# Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
|
||||
# error, crit, alert, emerg.
|
||||
# It is also possible to configure the loglevel for particular
|
||||
# modules, e.g.
|
||||
#LogLevel info ssl:warn
|
||||
|
||||
ErrorLog /dev/stdout
|
||||
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||
|
||||
# For most configuration files from conf-available/, which are
|
||||
# enabled or disabled at a global level, it is possible to
|
||||
# include a line for only one particular virtual host. For example the
|
||||
# following line enables the CGI configuration for this host only
|
||||
# after it has been globally disabled with "a2disconf".
|
||||
#Include conf-available/serve-cgi-bin.conf
|
||||
# Simple ddev technique to get a phpstatus
|
||||
Alias "/phpstatus" "/var/www/phpstatus.php"
|
||||
|
||||
</VirtualHost>
|
||||
|
||||
<VirtualHost *:443>
|
||||
SSLEngine on
|
||||
SSLCertificateFile /etc/ssl/certs/master.crt
|
||||
SSLCertificateKeyFile /etc/ssl/certs/master.key
|
||||
|
||||
# Workaround from https://mail-archives.apache.org/mod_mbox/httpd-users/201403.mbox/%3C49404A24C7FAD94BB7B45E86A9305F6214D04652@MSGEXSV21103.ent.wfb.bank.corp%3E
|
||||
# See also https://gist.github.com/nurtext/b6ac07ac7d8c372bc8eb
|
||||
|
||||
RewriteEngine On
|
||||
RewriteCond %{HTTP:X-Forwarded-Proto} =https
|
||||
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d
|
||||
RewriteRule ^(.+[^/])$ https://%{HTTP_HOST}$1/ [redirect,last]
|
||||
|
||||
SetEnvIf X-Forwarded-Proto "https" HTTPS=on
|
||||
|
||||
ServerAdmin webmaster@localhost
|
||||
DocumentRoot /var/www/html
|
||||
<Directory "/var/www/html/">
|
||||
AllowOverride All
|
||||
Allow from All
|
||||
</Directory>
|
||||
# Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
|
||||
# error, crit, alert, emerg.
|
||||
# It is also possible to configure the loglevel for particular
|
||||
# modules, e.g.
|
||||
#LogLevel info ssl:warn
|
||||
|
||||
ErrorLog /dev/stdout
|
||||
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||
|
||||
# For most configuration files from conf-available/, which are
|
||||
# enabled or disabled at a global level, it is possible to
|
||||
# include a line for only one particular virtual host. For example the
|
||||
# following line enables the CGI configuration for this host only
|
||||
# after it has been globally disabled with "a2disconf".
|
||||
#Include conf-available/serve-cgi-bin.conf
|
||||
# Simple ddev technique to get a phpstatus
|
||||
Alias "/phpstatus" "/var/www/phpstatus.php"
|
||||
|
||||
</VirtualHost>
|
||||
|
||||
<VirtualHost *:80>
|
||||
SetEnvIf X-Forwarded-Proto "https" HTTPS=on
|
||||
|
||||
DocumentRoot /var/www/{{EXTENSION_KEY}}/Documentation-GENERATED-temp
|
||||
ServerAlias docs.{{DDEV_SITENAME}}.ddev.site
|
||||
|
||||
<Directory "/var/www/{{EXTENSION_KEY}}/Documentation-GENERATED-temp/">
|
||||
AllowOverride All
|
||||
Allow from All
|
||||
|
||||
DirectoryIndex Index.html
|
||||
</Directory>
|
||||
|
||||
ErrorLog /dev/stdout
|
||||
Alias "/phpstatus" "/var/www/phpstatus.php"
|
||||
</VirtualHost>
|
||||
|
||||
<VirtualHost *:80>
|
||||
RewriteEngine On
|
||||
RewriteCond %{HTTP:X-Forwarded-Proto} =https
|
||||
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d
|
||||
RewriteRule ^(.+[^/])$ https://%{HTTP_HOST}$1/ [redirect,last]
|
||||
|
||||
SetEnvIf X-Forwarded-Proto "https" HTTPS=on
|
||||
|
||||
DocumentRoot /var/www/html/v11/public
|
||||
ServerAlias v11.{{DDEV_SITENAME}}.ddev.site
|
||||
|
||||
<Directory "/var/www/html/v11/">
|
||||
AllowOverride All
|
||||
Allow from All
|
||||
</Directory>
|
||||
|
||||
ErrorLog /dev/stdout
|
||||
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||
Alias "/phpstatus" "/var/www/phpstatus.php"
|
||||
</VirtualHost>
|
||||
|
||||
<VirtualHost *:80>
|
||||
RewriteEngine On
|
||||
RewriteCond %{HTTP:X-Forwarded-Proto} =https
|
||||
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d
|
||||
RewriteRule ^(.+[^/])$ https://%{HTTP_HOST}$1/ [redirect,last]
|
||||
|
||||
SetEnvIf X-Forwarded-Proto "https" HTTPS=on
|
||||
|
||||
DocumentRoot /var/www/html/v12/public
|
||||
ServerAlias v12.{{DDEV_SITENAME}}.ddev.site
|
||||
|
||||
<Directory "/var/www/html/v12/">
|
||||
AllowOverride All
|
||||
Allow from All
|
||||
</Directory>
|
||||
|
||||
ErrorLog /dev/stdout
|
||||
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||
Alias "/phpstatus" "/var/www/phpstatus.php"
|
||||
</VirtualHost>
|
||||
|
||||
<VirtualHost *:80>
|
||||
RewriteEngine On
|
||||
RewriteCond %{HTTP:X-Forwarded-Proto} =https
|
||||
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d
|
||||
RewriteRule ^(.+[^/])$ https://%{HTTP_HOST}$1/ [redirect,last]
|
||||
|
||||
SetEnvIf X-Forwarded-Proto "https" HTTPS=on
|
||||
|
||||
DocumentRoot /var/www/html/v13/public
|
||||
ServerAlias v13.{{DDEV_SITENAME}}.ddev.site
|
||||
|
||||
<Directory "/var/www/html/v13/">
|
||||
AllowOverride All
|
||||
Allow from All
|
||||
</Directory>
|
||||
|
||||
ErrorLog /dev/stdout
|
||||
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||
Alias "/phpstatus" "/var/www/phpstatus.php"
|
||||
</VirtualHost>
|
||||
|
||||
# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
|
||||
74
skills/typo3-ddev/assets/templates/commands/host/docs
Normal file
74
skills/typo3-ddev/assets/templates/commands/host/docs
Normal file
@@ -0,0 +1,74 @@
|
||||
#!/bin/bash
|
||||
|
||||
## Description: Render TYPO3 extension documentation to HTML
|
||||
## Usage: docs
|
||||
## Example: "ddev docs"
|
||||
|
||||
set -e
|
||||
|
||||
PROJECT_DIR="$(pwd)"
|
||||
DOC_SOURCE="${PROJECT_DIR}/Documentation"
|
||||
DOC_OUTPUT="${PROJECT_DIR}/Documentation-GENERATED-temp"
|
||||
|
||||
echo "📚 Rendering TYPO3 Extension Documentation"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
# Check if Documentation directory exists
|
||||
if [ ! -d "$DOC_SOURCE" ]; then
|
||||
echo "❌ Error: Documentation directory not found"
|
||||
echo "💡 Tip: Create a Documentation/ directory with Index.rst to get started"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if Index.rst exists
|
||||
if [ ! -f "$DOC_SOURCE/Index.rst" ]; then
|
||||
echo "❌ Error: Index.rst not found in Documentation/"
|
||||
echo "💡 Tip: Create Documentation/Index.rst as the main entry point"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "📖 Source: Documentation/"
|
||||
echo "📦 Output: Documentation-GENERATED-temp/"
|
||||
echo ""
|
||||
|
||||
# Clean previous output
|
||||
if [ -d "$DOC_OUTPUT" ]; then
|
||||
echo "🧹 Cleaning previous documentation build..."
|
||||
rm -rf "$DOC_OUTPUT"
|
||||
fi
|
||||
|
||||
# Render documentation using TYPO3's official docker image
|
||||
echo "🔨 Rendering documentation with TYPO3 render-guides..."
|
||||
echo ""
|
||||
|
||||
docker run --rm \
|
||||
-v "${PROJECT_DIR}:/project" \
|
||||
--user "$(id -u):$(id -g)" \
|
||||
ghcr.io/typo3-documentation/render-guides:latest \
|
||||
--no-progress \
|
||||
--output=/project/Documentation-GENERATED-temp \
|
||||
/project/Documentation 2>&1
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
# Check for output files (structure may vary)
|
||||
if [ -f "$DOC_OUTPUT/Result/project/0.0.0/Index.html" ] || \
|
||||
[ -f "$DOC_OUTPUT/Index.html" ] || \
|
||||
[ -d "$DOC_OUTPUT" ]; then
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "✅ Documentation rendered successfully!"
|
||||
echo ""
|
||||
echo "🌐 View at: https://docs.$(ddev describe -j | jq -r .raw.name).ddev.site/"
|
||||
echo "📁 Output: Documentation-GENERATED-temp/"
|
||||
echo ""
|
||||
else
|
||||
echo ""
|
||||
echo "⚠️ Build completed but output structure unclear"
|
||||
echo "📁 Check: Documentation-GENERATED-temp/"
|
||||
fi
|
||||
else
|
||||
echo ""
|
||||
echo "❌ Documentation rendering failed"
|
||||
echo "💡 Check your .rst files for syntax errors"
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
|
||||
## Description: Capture git info for landing page before container build
|
||||
## Usage: Runs automatically before ddev start
|
||||
## Example: "ddev start"
|
||||
|
||||
# Get git info from host
|
||||
GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
||||
GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
||||
GIT_PR=$(gh pr view --json number 2>/dev/null | jq -r '.number // "unknown"' || echo "unknown")
|
||||
|
||||
# Export for docker-compose to use
|
||||
export DDEV_GIT_BRANCH="$GIT_BRANCH"
|
||||
export DDEV_GIT_COMMIT="$GIT_COMMIT"
|
||||
export DDEV_GIT_PR="$GIT_PR"
|
||||
|
||||
echo "Git info captured: $GIT_BRANCH @ $GIT_COMMIT (PR: $GIT_PR)"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user