
Typo3 Ddev
- 39 installs
- 8 repo stars
- Updated August 2, 2026
- netresearch/typo3-ddev-skill
Helps with ai & agent building tasks.
About
typo3-ddev is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- typo3-ddev
- AI & Agent Building
- AI-coding skill
Typo3 Ddev by the numbers
- 39 all-time installs (skills.sh)
- +1 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #8,347 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/netresearch/typo3-ddev-skill --skill typo3-ddevAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 39 |
|---|---|
| repo stars | ★ 8 |
| Last updated | August 2, 2026 |
| Repository | netresearch/typo3-ddev-skill ↗ |
What it does
Helps with ai & agent building tasks.
Files
TYPO3 DDEV Setup Skill
CRITICAL: URL Scheme
NEVER guess URLs. Read `name:` from `.ddev/config.yaml`, then apply subdomain pattern:
https://v{VERSION}.{sitename}.ddev.site/typo3/ — e.g., https://v14.my-ext.ddev.site/typo3/
Landing: https://{sitename}.ddev.site/ · Docs: https://docs.{sitename}.ddev.site/
Each version has an Apache vhost (/var/www/html/v{VERSION}) via additional_hostnames. Never infer URLs from directory listings.
Credentials: admin / Joh316!!
Container Priority
1. .ddev/ exists → ddev exec 2. docker-compose.yml → docker compose exec 3. System tools only if no container. Always use project's configured PHP.
In-container file edits: docker cp in, ddev exec — not heredocs/php -r. See references/container-file-editing.md.
Quick Start
ddev start && ddev install-all # All versions (11/12/13/14)
ddev install-v14 # v14.3 LTS (default / gold standard)
ddev install-v13 # v13.4 LTSDatabase Selection
MariaDB 10.11 (default) · SQLite (simple, no SQL) · PostgreSQL 16 (GIS) · MySQL 8.0 (Oracle parity). See references/advanced-options.md.
PHP Management
php_version: "8.3" in config.yaml. Upgrade via .ddev/web-build/Dockerfile (apt-get dist-upgrade). Custom settings: .ddev/php/custom.ini. See references/0003-php-version-management.md.
TYPO3 Version Differences
| v12 | v13 | v14.3 LTS | |
|---|---|---|---|
| Setup | install:setup --use-existing-database | setup | setup |
| Activation | Auto (Composer) | extension:setup | extension:setup |
composer.json | optional | optional | required (classic mode, #108310) |
| Default theme | bootstrap-package | bootstrap-package | Camino (#108539) |
| Fluid | 2.x | 4.x | 5.x strict (#108148) |
| CKEditor | 41–42 | 41–42 | 47 |
See references/typo3-12-cli-changes.md.
Post-Setup Verification
ddev status, ddev describe, ddev exec -d /var/www/html/v13 vendor/bin/typo3 extension:list --active. See references/post-setup-verification.md.
Optional Services & Commands
- Valkey 8 (default) or Redis 7:
references/0001-valkey-default-with-redis-alternative.md - Ofelia scheduler: TYPO3 scheduler automation
ddev generate-makefile/ddev generate-index/ddev docsddev xdebug on· cache flush: see Troubleshooting
Extension Naming
Hyphens for composer (nr-llm), underscores for TYPO3 key (nr_llm). Source: composer.json name.
Troubleshooting
| Issue | Solution |
|---|---|
| Port conflict | router_http_port: "8080" / router_https_port: "8443" |
| Database exists | ddev mysql -e "DROP DATABASE v13; CREATE DATABASE v13;" |
| Extension not found | ddev exec -d /var/www/html/v13 vendor/bin/typo3 cache:flush |
| Windows health check | Add /phpstatus endpoint with php-fpm.sock |
| PCOV/pecl fails | apt-get install php${PHP_VERSION}-pcov |
| PHP settings ignored | Place in .ddev/php/custom.ini |
| Full cleanup | ddev delete --omit-snapshot --yes then remove Docker volumes |
References
| Topic | File |
|---|---|
| Prerequisites | references/prerequisites-validation.md |
| Quick start | references/quickstart.md |
| Advanced options | references/advanced-options.md |
| Post-setup | references/post-setup-verification.md |
| Branding/landing page | references/index-page-generation.md |
| ADR References | references/{0001,0002,0003}-*.md |
| Windows | references/windows-fixes.md, references/windows-optimizations.md |
| Docs rendering | references/documentation-rendering.md |
| Troubleshooting | references/troubleshooting.md |
# direnv configuration for {{EXTENSION_KEY}}
# Auto-generated by typo3-ddev-skill - Last updated: {{GENERATED_DATE}}
#
# 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: https://v13.{{DDEV_SITENAME}}.ddev.site"
echo ""
echo "🚀 Quick Start:"
echo " make up Complete startup (start + setup) ⭐"
echo " ├─ make start Start DDEV environment"
echo " │ └─ ddev start (internal: Docker containers)"
echo " └─ make setup Complete setup (docs + install)"
echo " ├─ ddev docs Render extension documentation"
echo " └─ ddev install-v13 Install TYPO3 13.4 LTS"
echo ""
echo "📚 DDEV Commands:"
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: php-cs-fixer, phpstan, phplint, rector"
echo "🔧 More Commands: make help"
# TYPO3 Extension DDEV Apache Configuration - Main Site
#
# IMPORTANT: This file is gitignored by ddev's default .gitignore!
# ddev ignores: /apache/apache-site.conf
#
# For multi-version TYPO3 setups, use the separate COMMITTABLE config files:
# - docs.conf - Documentation subdomain (docs.{sitename}.ddev.site)
# - v11.conf - TYPO3 v11 (/var/www/html/v11/public)
# - v12.conf - TYPO3 v12 (/var/www/html/v12/public)
# - v13.conf - TYPO3 v13 (/var/www/html/v13/public)
# - v14.conf - TYPO3 v14 (/var/www/html/v14/public)
#
# Only copy the version files you need for your project.
#
# NOTE: Use ${DDEV_SITENAME} (Apache env var), NOT {{DDEV_SITENAME}} (template placeholder)
# Apache evaluates ${DDEV_SITENAME} at runtime from DDEV's environment
ServerName ${DDEV_SITENAME}.ddev.site
<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
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
<Directory "/var/www/html/">
AllowOverride All
Require all granted
</Directory>
ErrorLog /dev/stdout
CustomLog ${APACHE_LOG_DIR}/access.log combined
LimitRequestFieldSize 16380
Alias "/phpstatus" "/var/www/phpstatus.php"
</VirtualHost>
<VirtualHost *:443>
SSLEngine on
SSLCertificateFile /etc/ssl/certs/master.crt
SSLCertificateKeyFile /etc/ssl/certs/master.key
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
Require all granted
</Directory>
ErrorLog /dev/stdout
CustomLog ${APACHE_LOG_DIR}/access.log combined
LimitRequestFieldSize 16380
Alias "/phpstatus" "/var/www/phpstatus.php"
</VirtualHost>
# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
# Documentation subdomain VirtualHost
# Routes docs.{sitename}.ddev.site to Documentation-GENERATED-temp
# NOTE: This file CAN be committed (apache-site.conf is gitignored by ddev)
<VirtualHost *:80>
SetEnvIf X-Forwarded-Proto "https" HTTPS=on
DocumentRoot /var/www/html/Documentation-GENERATED-temp
ServerAlias docs.${DDEV_SITENAME}.ddev.site
<Directory "/var/www/html/Documentation-GENERATED-temp/">
AllowOverride All
Require all granted
DirectoryIndex Index.html index.html
</Directory>
ErrorLog /dev/stdout
Alias "/phpstatus" "/var/www/phpstatus.php"
</VirtualHost>
<VirtualHost *:443>
SSLEngine on
SSLCertificateFile /etc/ssl/certs/master.crt
SSLCertificateKeyFile /etc/ssl/certs/master.key
SetEnvIf X-Forwarded-Proto "https" HTTPS=on
DocumentRoot /var/www/html/Documentation-GENERATED-temp
ServerAlias docs.${DDEV_SITENAME}.ddev.site
<Directory "/var/www/html/Documentation-GENERATED-temp/">
AllowOverride All
Require all granted
DirectoryIndex Index.html index.html
</Directory>
ErrorLog /dev/stdout
Alias "/phpstatus" "/var/www/phpstatus.php"
</VirtualHost>
# TYPO3 v11 VirtualHost
# Routes v11.{sitename}.ddev.site to /var/www/html/v11/public
# NOTE: This file CAN be committed (apache-site.conf is gitignored by ddev)
<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/public/">
AllowOverride All
Require all granted
</Directory>
ErrorLog /dev/stdout
CustomLog ${APACHE_LOG_DIR}/access.log combined
Alias "/phpstatus" "/var/www/phpstatus.php"
</VirtualHost>
<VirtualHost *:443>
SSLEngine on
SSLCertificateFile /etc/ssl/certs/master.crt
SSLCertificateKeyFile /etc/ssl/certs/master.key
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/public/">
AllowOverride All
Require all granted
</Directory>
ErrorLog /dev/stdout
CustomLog ${APACHE_LOG_DIR}/access.log combined
Alias "/phpstatus" "/var/www/phpstatus.php"
</VirtualHost>
# TYPO3 v12 VirtualHost
# Routes v12.{sitename}.ddev.site to /var/www/html/v12/public
# NOTE: This file CAN be committed (apache-site.conf is gitignored by ddev)
<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/public/">
AllowOverride All
Require all granted
</Directory>
ErrorLog /dev/stdout
CustomLog ${APACHE_LOG_DIR}/access.log combined
Alias "/phpstatus" "/var/www/phpstatus.php"
</VirtualHost>
<VirtualHost *:443>
SSLEngine on
SSLCertificateFile /etc/ssl/certs/master.crt
SSLCertificateKeyFile /etc/ssl/certs/master.key
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/public/">
AllowOverride All
Require all granted
</Directory>
ErrorLog /dev/stdout
CustomLog ${APACHE_LOG_DIR}/access.log combined
Alias "/phpstatus" "/var/www/phpstatus.php"
</VirtualHost>
# TYPO3 v13 VirtualHost
# Routes v13.{sitename}.ddev.site to /var/www/html/v13/public
# NOTE: This file CAN be committed (apache-site.conf is gitignored by ddev)
<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/public/">
AllowOverride All
Require all granted
</Directory>
ErrorLog /dev/stdout
CustomLog ${APACHE_LOG_DIR}/access.log combined
Alias "/phpstatus" "/var/www/phpstatus.php"
</VirtualHost>
<VirtualHost *:443>
SSLEngine on
SSLCertificateFile /etc/ssl/certs/master.crt
SSLCertificateKeyFile /etc/ssl/certs/master.key
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/public/">
AllowOverride All
Require all granted
</Directory>
ErrorLog /dev/stdout
CustomLog ${APACHE_LOG_DIR}/access.log combined
Alias "/phpstatus" "/var/www/phpstatus.php"
</VirtualHost>
# TYPO3 v14 VirtualHost
# Routes v14.{sitename}.ddev.site to /var/www/html/v14/public
# NOTE: This file CAN be committed (apache-site.conf is gitignored by ddev)
<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/v14/public
ServerAlias v14.${DDEV_SITENAME}.ddev.site
<Directory "/var/www/html/v14/public/">
AllowOverride All
Require all granted
</Directory>
ErrorLog /dev/stdout
CustomLog ${APACHE_LOG_DIR}/access.log combined
Alias "/phpstatus" "/var/www/phpstatus.php"
</VirtualHost>
<VirtualHost *:443>
SSLEngine on
SSLCertificateFile /etc/ssl/certs/master.crt
SSLCertificateKeyFile /etc/ssl/certs/master.key
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/v14/public
ServerAlias v14.${DDEV_SITENAME}.ddev.site
<Directory "/var/www/html/v14/public/">
AllowOverride All
Require all granted
</Directory>
ErrorLog /dev/stdout
CustomLog ${APACHE_LOG_DIR}/access.log combined
Alias "/phpstatus" "/var/www/phpstatus.php"
</VirtualHost>
#!/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
#!/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"' 2>/dev/null || echo "unknown")
# Write docker-compose override with current git info
# This ensures build args are always up-to-date (exports don't work for build args)
cat > .ddev/docker-compose.git-info.yaml << EOF
services:
web:
build:
args:
GIT_BRANCH: "${GIT_BRANCH}"
GIT_COMMIT: "${GIT_COMMIT}"
GIT_PR: "${GIT_PR}"
EOF
echo "Git info updated: ${GIT_BRANCH} @ ${GIT_COMMIT} (PR: ${GIT_PR})"
#!/bin/bash
## Description: Complete setup - docs, install TYPO3, configure extension
## Usage: setup
## Example: "ddev setup"
set -e
echo "🚀 Complete DDEV Environment Setup"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
# Step 1: Render documentation
echo "📚 Step 1/3: Rendering documentation..."
if [ -d "/var/www/${EXTENSION_KEY}/Documentation" ]; then
ddev docs
else
echo "⚠️ No Documentation/ directory found, skipping docs rendering"
fi
echo ""
# Step 2: Install TYPO3
echo "🌐 Step 2/3: Installing TYPO3..."
ddev exec /mnt/ddev_config/commands/web/install-all
echo ""
# Done
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "✅ Setup Complete!"
echo ""
echo "🌐 Access Your Environment:"
echo " Overview: https://${DDEV_SITENAME}.ddev.site/"
echo " Documentation: 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 "🔑 Backend Login:"
echo " Username: admin"
echo " Password: Joh316!!"
echo ""
#!/bin/bash
## Description: Install all TYPO3 versions (11, 12, 13, 14) with your extension
## Usage: install-all
## Example: ddev install-all
echo "Installing TYPO3 11.5 LTS..."
/mnt/ddev_config/commands/web/install-v11
echo "Installing TYPO3 12.4 LTS..."
/mnt/ddev_config/commands/web/install-v12
echo "Installing TYPO3 13.4 LTS..."
/mnt/ddev_config/commands/web/install-v13
echo "Installing TYPO3 14.3 LTS (default / gold standard)..."
/mnt/ddev_config/commands/web/install-v14
echo "✅ All TYPO3 versions installed successfully!"
#!/bin/bash
## Description: Install TYPO3 11.5 LTS with your extension
## Usage: install-v11
## Example: ddev install-v11
VERSION=v11
rm -rf /var/www/html/$VERSION/*
mkdir -p /var/www/html/$VERSION/
echo "{}" > /var/www/html/$VERSION/composer.json
composer config extra.typo3/cms.web-dir public -d /var/www/html/$VERSION
composer config repositories.$EXTENSION_KEY path ../../$EXTENSION_KEY -d /var/www/html/$VERSION
composer config --no-plugins allow-plugins.typo3/cms-composer-installers true -d /var/www/html/$VERSION
composer config --no-plugins allow-plugins.typo3/class-alias-loader true -d /var/www/html/$VERSION
composer req t3/cms:'^11' $PACKAGE_NAME:'*@dev' --no-progress -n -d /var/www/html/$VERSION
composer req typo3/cms-extensionmanager --no-progress -n -d /var/www/html/$VERSION
composer req --dev typo3/cms-styleguide --no-progress -n -d /var/www/html/$VERSION
composer req typo3/cms-introduction --no-progress -n -d /var/www/html/$VERSION
cd /var/www/html/$VERSION
TYPO3_INSTALL_DB_DBNAME=$VERSION
vendor/bin/typo3cms install:setup -n --database-name $VERSION
vendor/bin/typo3cms configuration:set 'BE/debug' 1
vendor/bin/typo3cms configuration:set 'FE/debug' 1
vendor/bin/typo3cms configuration:set 'SYS/devIPmask' '*'
vendor/bin/typo3cms configuration:set 'SYS/displayErrors' 1
# Create AdditionalConfiguration.php for trustedHostsPattern (configuration:set doesn't work reliably for this)
# Note: TYPO3 v11 uses public/typo3conf/ path
echo '<?php' > /var/www/html/$VERSION/public/typo3conf/AdditionalConfiguration.php
echo '$GLOBALS["TYPO3_CONF_VARS"]["SYS"]["trustedHostsPattern"] = ".*";' >> /var/www/html/$VERSION/public/typo3conf/AdditionalConfiguration.php
echo '$GLOBALS["TYPO3_CONF_VARS"]["SYS"]["features"]["security.backend.enforceReferrer"] = false;' >> /var/www/html/$VERSION/public/typo3conf/AdditionalConfiguration.php
vendor/bin/typo3cms configuration:set 'MAIL/transport' 'smtp'
vendor/bin/typo3cms configuration:set 'MAIL/transport_smtp_server' 'localhost:1025'
vendor/bin/typo3cms configuration:set 'GFX/processor' 'ImageMagick'
vendor/bin/typo3cms configuration:set 'GFX/processor_path' '/usr/bin/'
vendor/bin/typo3cms configuration:set 'GFX/processor_path_lzw' '/usr/bin/'
sed -i "/'deprecations'/,/^[[:space:]]*'disabled' => true,/s/'disabled' => true,/'disabled' => false,/" /var/www/html/$VERSION/public/typo3conf/LocalConfiguration.php
sed -i -e "s/base: ht\//base: \//g" /var/www/html/$VERSION/config/sites/main/config.yaml
sed -i -e 's/base: \/en\//base: \//g' /var/www/html/$VERSION/config/sites/main/config.yaml
# Generate styleguide demo content for UI pattern reference
vendor/bin/typo3cms styleguide:generate
echo "📐 Styleguide demo content generated"
vendor/bin/typo3cms cache:flush
echo ""
echo "✅ TYPO3 $VERSION installation complete!"
echo "📦 Your extension is activated and ready to use"
echo "📐 TYPO3 Styleguide is available for UI pattern reference"
echo "📦 Introduction Package installed with demo content (86+ pages, 226+ content elements)"
# Auto-configure extension if configure script exists
if [ -f "/mnt/ddev_config/commands/web/configure-{{EXTENSION_KEY}}" ]; then
echo ""
echo "🔧 Auto-configuring {{EXTENSION_KEY}} extension..."
/mnt/ddev_config/commands/web/configure-{{EXTENSION_KEY}} $VERSION
fi
#!/bin/bash
## Description: Install TYPO3 12.4 LTS with your extension
## Usage: install-v12
## Example: ddev install-v12
VERSION=v12
rm -rf /var/www/html/$VERSION/*
mkdir -p /var/www/html/$VERSION/
echo "{}" > /var/www/html/$VERSION/composer.json
composer config extra.typo3/cms.web-dir public -d /var/www/html/$VERSION
composer config repositories.$EXTENSION_KEY path ../../$EXTENSION_KEY -d /var/www/html/$VERSION
composer config --no-plugins allow-plugins.typo3/cms-composer-installers true -d /var/www/html/$VERSION
composer config --no-plugins allow-plugins.typo3/class-alias-loader true -d /var/www/html/$VERSION
composer req t3/cms:'^12' $PACKAGE_NAME:'*@dev' --no-progress -n -d /var/www/html/$VERSION
composer req typo3/cms-extensionmanager --no-progress -n -d /var/www/html/$VERSION
composer req --dev typo3/cms-styleguide --no-progress -n -d /var/www/html/$VERSION
composer req typo3/cms-introduction --no-progress -n -d /var/www/html/$VERSION
cd /var/www/html/$VERSION
# Clean database before setup (prevents "database already exists" errors)
mysql -h db -u root -p"root" -e "DROP DATABASE IF EXISTS ${VERSION};"
mysql -h db -u root -p"root" -e "CREATE DATABASE ${VERSION};"
# TYPO3 12.4+ uses the 'setup' command (install:setup is deprecated)
vendor/bin/typo3 setup -n --dbname=$VERSION --password=$TYPO3_DB_PASSWORD --create-site="https://${VERSION}.{{DDEV_SITENAME}}.ddev.site" --admin-user-password=$TYPO3_SETUP_ADMIN_PASSWORD
vendor/bin/typo3 configuration:set 'BE/debug' 1
vendor/bin/typo3 configuration:set 'FE/debug' 1
vendor/bin/typo3 configuration:set 'SYS/devIPmask' '*'
vendor/bin/typo3 configuration:set 'SYS/displayErrors' 1
# Create additional.php for trustedHostsPattern (configuration:set doesn't work reliably for this)
mkdir -p /var/www/html/$VERSION/config/system
echo '<?php' > /var/www/html/$VERSION/config/system/additional.php
echo '$GLOBALS["TYPO3_CONF_VARS"]["SYS"]["trustedHostsPattern"] = ".*";' >> /var/www/html/$VERSION/config/system/additional.php
echo '$GLOBALS["TYPO3_CONF_VARS"]["SYS"]["features"]["security.backend.enforceReferrer"] = false;' >> /var/www/html/$VERSION/config/system/additional.php
vendor/bin/typo3 configuration:set 'MAIL/transport' 'smtp'
vendor/bin/typo3 configuration:set 'MAIL/transport_smtp_server' 'localhost:1025'
vendor/bin/typo3 configuration:set 'MAIL/defaultMailFromAddress' 'admin@example.com'
vendor/bin/typo3 configuration:set 'GFX/processor' 'ImageMagick'
vendor/bin/typo3 configuration:set 'GFX/processor_path' '/usr/bin/'
sed -i "/'deprecations'/,/^[[:space:]]*'disabled' => true,/s/'disabled' => true,/'disabled' => false,/" /var/www/html/$VERSION/config/system/settings.php
# Run extension:setup to create database tables and import static data
vendor/bin/typo3 extension:setup
# Generate styleguide demo content for UI pattern reference
vendor/bin/typo3 styleguide:generate
echo "📐 Styleguide demo content generated"
vendor/bin/typo3 cache:flush
echo ""
echo "✅ TYPO3 $VERSION installation complete!"
echo "📦 Your extension is activated and ready to use"
echo "📐 TYPO3 Styleguide is available for UI pattern reference"
echo "📦 Introduction Package installed with demo content (86+ pages, 226+ content elements)"
# Auto-configure extension if configure script exists
if [ -f "/mnt/ddev_config/commands/web/configure-{{EXTENSION_KEY}}" ]; then
echo ""
echo "🔧 Auto-configuring {{EXTENSION_KEY}} extension..."
/mnt/ddev_config/commands/web/configure-{{EXTENSION_KEY}} $VERSION
fi
#!/bin/bash
## Description: Install TYPO3 13.4 LTS with your extension
## Usage: install-v13
## Example: ddev install-v13
VERSION=v13
rm -rf /var/www/html/$VERSION/*
mkdir -p /var/www/html/$VERSION/
echo "{}" > /var/www/html/$VERSION/composer.json
composer config extra.typo3/cms.web-dir public -d /var/www/html/$VERSION
composer config repositories.$EXTENSION_KEY path ../../$EXTENSION_KEY -d /var/www/html/$VERSION
composer config --no-plugins allow-plugins.typo3/cms-composer-installers true -d /var/www/html/$VERSION
composer config --no-plugins allow-plugins.typo3/class-alias-loader true -d /var/www/html/$VERSION
composer req t3/cms:'^13' $PACKAGE_NAME:'*@dev' --no-progress -n -d /var/www/html/$VERSION
composer req typo3/cms-extensionmanager --no-progress -n -d /var/www/html/$VERSION
composer req --dev typo3/cms-styleguide --no-progress -n -d /var/www/html/$VERSION
composer req typo3/cms-introduction --no-progress -n -d /var/www/html/$VERSION
cd /var/www/html/$VERSION
mysql -h db -u root -p"root" -e "DROP DATABASE IF EXISTS ${VERSION};"
mysql -h db -u root -p"root" -e "CREATE DATABASE ${VERSION};"
vendor/bin/typo3 setup -n --dbname=$VERSION --password=$TYPO3_DB_PASSWORD --create-site="https://${VERSION}.{{DDEV_SITENAME}}.ddev.site" --admin-user-password=$TYPO3_SETUP_ADMIN_PASSWORD
vendor/bin/typo3 configuration:set 'BE/debug' 1
vendor/bin/typo3 configuration:set 'FE/debug' 1
vendor/bin/typo3 configuration:set 'SYS/devIPmask' '*'
vendor/bin/typo3 configuration:set 'SYS/displayErrors' 1
# Create additional.php for trustedHostsPattern (configuration:set doesn't work reliably for this)
mkdir -p /var/www/html/$VERSION/config/system
echo '<?php' > /var/www/html/$VERSION/config/system/additional.php
echo '$GLOBALS["TYPO3_CONF_VARS"]["SYS"]["trustedHostsPattern"] = ".*";' >> /var/www/html/$VERSION/config/system/additional.php
echo '$GLOBALS["TYPO3_CONF_VARS"]["SYS"]["features"]["security.backend.enforceReferrer"] = false;' >> /var/www/html/$VERSION/config/system/additional.php
vendor/bin/typo3 configuration:set 'MAIL/transport' 'smtp'
vendor/bin/typo3 configuration:set 'MAIL/transport_smtp_server' 'localhost:1025'
vendor/bin/typo3 configuration:set 'MAIL/defaultMailFromAddress' 'admin@example.com'
vendor/bin/typo3 configuration:set 'GFX/processor' 'ImageMagick'
vendor/bin/typo3 configuration:set 'GFX/processor_path' '/usr/bin/'
sed -i "/'deprecations'/,/^[[:space:]]*'disabled' => true,/s/'disabled' => true,/'disabled' => false,/" /var/www/html/$VERSION/config/system/settings.php
vendor/bin/typo3 extension:setup
# Configure site sets for v13 (adds Bootstrap Package for frontend rendering)
if [ -f config/sites/main/config.yaml ]; then
if ! grep -q "^dependencies:" config/sites/main/config.yaml; then
echo "" >> config/sites/main/config.yaml
echo "dependencies:" >> config/sites/main/config.yaml
echo " - bootstrap-package/full" >> config/sites/main/config.yaml
echo "🎨 Site sets configured with Bootstrap Package"
fi
fi
# Generate styleguide demo content for UI pattern reference
vendor/bin/typo3 styleguide:generate
echo "📐 Styleguide demo content generated"
vendor/bin/typo3 cache:flush
echo ""
echo "✅ TYPO3 $VERSION installation complete!"
echo "📦 Your extension is activated and ready to use"
echo "📐 TYPO3 Styleguide is available for UI pattern reference"
echo "📦 Introduction Package installed with demo content (86+ pages, 226+ content elements)"
# Auto-configure extension if configure script exists
if [ -f "/mnt/ddev_config/commands/web/configure-{{EXTENSION_KEY}}" ]; then
echo ""
echo "🔧 Auto-configuring {{EXTENSION_KEY}} extension..."
/mnt/ddev_config/commands/web/configure-{{EXTENSION_KEY}} $VERSION
fi
#!/bin/bash
## Description: Install TYPO3 14.3 LTS with your extension (default / gold standard)
## Usage: install-v14
## Example: ddev install-v14
VERSION=v14
rm -rf /var/www/html/$VERSION/*
mkdir -p /var/www/html/$VERSION/
echo "{}" > /var/www/html/$VERSION/composer.json
composer config extra.typo3/cms.web-dir public -d /var/www/html/$VERSION
composer config repositories.$EXTENSION_KEY path ../../$EXTENSION_KEY -d /var/www/html/$VERSION
composer config --no-plugins allow-plugins.typo3/cms-composer-installers true -d /var/www/html/$VERSION
composer config --no-plugins allow-plugins.typo3/class-alias-loader true -d /var/www/html/$VERSION
composer req t3/cms:'^14' $PACKAGE_NAME:'*@dev' --no-progress -n -d /var/www/html/$VERSION
composer req typo3/cms-extensionmanager --no-progress -n -d /var/www/html/$VERSION
composer req --dev typo3/cms-styleguide --no-progress -n -d /var/www/html/$VERSION
composer req typo3/cms-introduction --no-progress -n -d /var/www/html/$VERSION
cd /var/www/html/$VERSION
mysql -h db -u root -p"root" -e "DROP DATABASE IF EXISTS ${VERSION};"
mysql -h db -u root -p"root" -e "CREATE DATABASE ${VERSION};"
vendor/bin/typo3 setup -n --dbname=$VERSION --password=$TYPO3_DB_PASSWORD --create-site="https://${VERSION}.{{DDEV_SITENAME}}.ddev.site" --admin-user-password=$TYPO3_SETUP_ADMIN_PASSWORD
vendor/bin/typo3 configuration:set 'BE/debug' 1
vendor/bin/typo3 configuration:set 'FE/debug' 1
vendor/bin/typo3 configuration:set 'SYS/devIPmask' '*'
vendor/bin/typo3 configuration:set 'SYS/displayErrors' 1
# Create additional.php for trustedHostsPattern (configuration:set doesn't work reliably for this)
mkdir -p /var/www/html/$VERSION/config/system
echo '<?php' > /var/www/html/$VERSION/config/system/additional.php
echo '$GLOBALS["TYPO3_CONF_VARS"]["SYS"]["trustedHostsPattern"] = ".*";' >> /var/www/html/$VERSION/config/system/additional.php
echo '$GLOBALS["TYPO3_CONF_VARS"]["SYS"]["features"]["security.backend.enforceReferrer"] = false;' >> /var/www/html/$VERSION/config/system/additional.php
vendor/bin/typo3 configuration:set 'MAIL/transport' 'smtp'
vendor/bin/typo3 configuration:set 'MAIL/transport_smtp_server' 'localhost:1025'
vendor/bin/typo3 configuration:set 'MAIL/defaultMailFromAddress' 'admin@example.com'
vendor/bin/typo3 configuration:set 'GFX/processor' 'ImageMagick'
vendor/bin/typo3 configuration:set 'GFX/processor_path' '/usr/bin/'
sed -i "/'deprecations'/,/^[[:space:]]*'disabled' => true,/s/'disabled' => true,/'disabled' => false,/" /var/www/html/$VERSION/config/system/settings.php
vendor/bin/typo3 extension:setup
# Configure site sets for v14 (adds Bootstrap Package for frontend rendering)
# Note: v14 ships Camino as default theme (#108539); remove the Bootstrap Package dep if you
# prefer Camino, or layer bootstrap-package on top.
if [ -f config/sites/main/config.yaml ]; then
if ! grep -q "^dependencies:" config/sites/main/config.yaml; then
echo "" >> config/sites/main/config.yaml
echo "dependencies:" >> config/sites/main/config.yaml
echo " - bootstrap-package/full" >> config/sites/main/config.yaml
echo "🎨 Site sets configured with Bootstrap Package"
fi
fi
# Generate styleguide demo content for UI pattern reference
vendor/bin/typo3 styleguide:generate
echo "📐 Styleguide demo content generated"
vendor/bin/typo3 cache:flush
echo ""
echo "✅ TYPO3 $VERSION (v14.3 LTS) installation complete!"
echo "📦 Your extension is activated and ready to use"
echo "📐 TYPO3 Styleguide is available for UI pattern reference"
echo "📦 Introduction Package installed with demo content"
echo "🆕 v14 highlights: Camino theme (site-set opt-in), Context Panel, redesigned DocHeader, Fluid 5, Extbase #[Authorize]/#[RateLimit]"
# Auto-configure extension if configure script exists
if [ -f "/mnt/ddev_config/commands/web/configure-{{EXTENSION_KEY}}" ]; then
echo ""
echo "🔧 Auto-configuring {{EXTENSION_KEY}} extension..."
/mnt/ddev_config/commands/web/configure-{{EXTENSION_KEY}} $VERSION
fi
#!/bin/bash
## Description: Auto-configure your TYPO3 extension with demo content and settings
## Usage: configure-extension [VERSION]
## Example: ddev configure-extension v13
VERSION=${1:-v13}
INSTALL_DIR=/var/www/html/$VERSION
echo "🔧 Configuring {{EXTENSION_KEY}} extension for $VERSION..."
# Install Introduction Package for demo content
echo "📦 Installing TYPO3 Introduction Package..."
composer require typo3/cms-introduction --no-progress -d $INSTALL_DIR
# Activate Introduction extension
echo "✅ Activating Introduction extension..."
cd $INSTALL_DIR
vendor/bin/typo3 extension:setup --extension=introduction
# Remove default "main" site created by typo3 setup (Introduction creates its own site)
echo "🧹 Removing default site (Introduction package provides its own)..."
rm -rf $INSTALL_DIR/config/sites/main
# Optional: Create site package for extension-specific configuration
# Uncomment and customize for extensions needing RTE, TsConfig, or TypoScript setup
# echo "📝 Creating site configuration package..."
# mkdir -p $INSTALL_DIR/public/typo3conf/ext/site_config/Configuration
#
# cat > $INSTALL_DIR/public/typo3conf/ext/site_config/ext_emconf.php << 'EOF'
# <?php
# $EM_CONF[$_EXTKEY] = [
# 'title' => 'Site Configuration',
# 'description' => 'Configuration for {{EXTENSION_KEY}}',
# 'category' => 'templates',
# 'state' => 'stable',
# 'version' => '1.0.0',
# 'constraints' => [
# 'depends' => [
# 'typo3' => '13.4.0-13.4.99',
# '{{EXTENSION_KEY}}' => '',
# ],
# ],
# ];
# EOF
# Example: RTE configuration for CKEditor plugins
# IMPORTANT: When defining editor.config, YAML replaces the entire block instead of merging!
# DO NOT override importModules or externalPlugins - let Plugin.yaml handle them.
#
# mkdir -p $INSTALL_DIR/public/typo3conf/ext/site_config/Configuration/RTE
#
# cat > $INSTALL_DIR/public/typo3conf/ext/site_config/Configuration/RTE/Default.yaml << 'EOF'
# imports:
# - { resource: "EXT:rte_ckeditor/Configuration/RTE/Default.yaml" }
# - { resource: "EXT:{{EXTENSION_KEY}}/Configuration/RTE/Plugin.yaml" }
#
# editor:
# config:
# # IMPORTANT: DO NOT override importModules here!
# # Plugin.yaml provides: importModules: ['@vendor/extension/Plugins/myplugin.js']
# # Plugin.yaml provides: externalPlugins: { myplugin: { route: ... } }
# # If you need html-support, add it to Plugin.yaml or merge arrays properly
#
# toolbar:
# items:
# - heading
# - '|'
# - bold
# - italic
# # Add your custom toolbar buttons here
# EOF
# Example: Page TSConfig
# mkdir -p $INSTALL_DIR/public/typo3conf/ext/site_config/Configuration/TsConfig/Page
#
# cat > $INSTALL_DIR/public/typo3conf/ext/site_config/Configuration/TsConfig/Page/Config.tsconfig << 'EOF'
# # Add your Page TSConfig here
# # Example: RTE.default.preset = default
# EOF
# IMPORTANT: If creating site_config extension, you must register it!
# Since site_config is not a composer-installed extension, ext_localconf.php
# and ext_tables.php won't be loaded automatically. Instead:
#
# 1. Register RTE preset in AdditionalConfiguration.php:
# cat >> $INSTALL_DIR/config/system/additional.php << 'EOF'
#
# // Register custom RTE preset
# $GLOBALS['TYPO3_CONF_VARS']['RTE']['Presets']['default'] = 'EXT:site_config/Configuration/RTE/Default.yaml';
# EOF
#
# 2. Add Page TSConfig to site configuration:
# cat >> $INSTALL_DIR/config/sites/introduction/config.yaml << 'EOF'
# pagesTsConfig:
# 'RTE.default.preset': 'default'
# EOF
# Flush caches
echo "🧹 Flushing caches..."
cd $INSTALL_DIR
vendor/bin/typo3 cache:flush
echo "✅ Extension configuration complete!"
echo ""
echo "📌 Access your TYPO3 installation:"
echo " Frontend: https://$VERSION.{{DDEV_SITENAME}}.ddev.site/"
echo " Backend: https://$VERSION.{{DDEV_SITENAME}}.ddev.site/typo3/"
echo " Username: admin"
echo " Password: Joh316!!"
echo ""
echo "🎨 Your extension is now configured and ready to test!"
#!/bin/bash
## Description: Generate index.html overview page with branded template
## Usage: generate-index
## Example: ddev generate-index
set -e
# =============================================================================
# VARIABLE EXTRACTION
# =============================================================================
# Get DDEV project name
DDEV_PROJECT=$(ddev describe -j 2>/dev/null | jq -r '.name' || basename "$(pwd)")
# Get extension key from directory name
EXTENSION_KEY=$(basename "$(pwd)")
# Extract data from composer.json if available
COMPOSER_FILE="/var/www/html/composer.json"
if [[ -f "$COMPOSER_FILE" ]]; then
COMPOSER_PACKAGE=$(jq -r '.name // empty' "$COMPOSER_FILE")
EXTENSION_NAME=$(jq -r '.extra.typo3.extension-key // .extra["extension-key"] // empty' "$COMPOSER_FILE")
EXTENSION_DESCRIPTION=$(jq -r '.description // empty' "$COMPOSER_FILE")
# Extract GitHub repo from homepage or support.source
GITHUB_REPO=$(jq -r '.homepage // .support.source // empty' "$COMPOSER_FILE" | grep -oE 'https://github.com/[^/]+/[^/\"]+' || echo "")
# Fallback extension name from composer package
if [[ -z "$EXTENSION_NAME" && -n "$COMPOSER_PACKAGE" ]]; then
EXTENSION_NAME=$(echo "$COMPOSER_PACKAGE" | sed 's/.*\///' | sed 's/-/_/g')
fi
fi
# Fallback values
EXTENSION_NAME=${EXTENSION_NAME:-$EXTENSION_KEY}
EXTENSION_DESCRIPTION=${EXTENSION_DESCRIPTION:-"TYPO3 Extension Development Environment"}
COMPOSER_PACKAGE=${COMPOSER_PACKAGE:-"vendor/$EXTENSION_KEY"}
GITHUB_REPO=${GITHUB_REPO:-"https://github.com/example/$EXTENSION_KEY"}
# Derive TER extension key from composer package or extension key
# Format: vendor/t3x-ext-name -> ext_name or vendor/ext -> ext
TER_EXTENSION_KEY=$(echo "$COMPOSER_PACKAGE" | sed 's/.*\///' | sed 's/^t3x-//' | sed 's/^nr-//' | sed 's/-/_/g')
# Make extension name human-readable for display
EXTENSION_DISPLAY_NAME=$(echo "$EXTENSION_NAME" | sed 's/_/ /g' | sed 's/-/ /g' | sed 's/\b\(.\)/\u\1/g')
# =============================================================================
# GIT INFORMATION
# =============================================================================
GIT_BRANCH="main"
GIT_COMMIT_SHORT="0000000"
GIT_COMMIT_MSG="No commit"
if command -v git &> /dev/null && git rev-parse --is-inside-work-tree &> /dev/null 2>&1; then
GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main")
GIT_COMMIT_SHORT=$(git rev-parse --short HEAD 2>/dev/null || echo "0000000")
GIT_COMMIT_MSG=$(git log -1 --pretty=%s 2>/dev/null | head -c 50 || echo "No commit")
# Escape special characters for sed
GIT_COMMIT_MSG=$(echo "$GIT_COMMIT_MSG" | sed 's/[&/\]/\\&/g')
fi
# =============================================================================
# PR INFORMATION (GitHub Actions environment)
# =============================================================================
PR_NUMBER=""
PR_URL=""
PR_TITLE=""
# Check for GitHub Actions PR environment variables
if [[ -n "${GITHUB_HEAD_REF:-}" ]]; then
# We're in a PR context
PR_NUMBER="${GITHUB_EVENT_PULL_REQUEST_NUMBER:-}"
if [[ -n "$PR_NUMBER" && -n "${GITHUB_REPOSITORY:-}" ]]; then
PR_URL="https://github.com/${GITHUB_REPOSITORY}/pull/${PR_NUMBER}"
PR_TITLE="${GITHUB_EVENT_PULL_REQUEST_TITLE:-PR #$PR_NUMBER}"
fi
fi
# =============================================================================
# TEMPLATE SELECTION
# =============================================================================
TEMPLATE_DIR="/var/www/html/.ddev"
IS_NETRESEARCH=false
# Detect Netresearch project by checking composer.json vendor
if [[ -n "$COMPOSER_PACKAGE" ]]; then
VENDOR=$(echo "$COMPOSER_PACKAGE" | cut -d'/' -f1)
if [[ "$VENDOR" == "netresearch" ]]; then
IS_NETRESEARCH=true
fi
fi
# Select template based on vendor
if [[ "$IS_NETRESEARCH" == true ]]; then
TEMPLATE_FILE="$TEMPLATE_DIR/index.html.netresearch.template"
echo "🔵 Detected Netresearch project - using Netresearch branded template"
else
TEMPLATE_FILE="$TEMPLATE_DIR/index.html.typo3.template"
echo "🟠 Using TYPO3 branded template"
fi
# Fallback to TYPO3 template if selected template not found
if [[ ! -f "$TEMPLATE_FILE" ]]; then
TEMPLATE_FILE="$TEMPLATE_DIR/index.html.typo3.template"
echo "⚠️ Selected template not found, falling back to TYPO3 template"
fi
if [[ ! -f "$TEMPLATE_FILE" ]]; then
echo "❌ Error: No template file found!"
exit 1
fi
# =============================================================================
# GENERATE INDEX.HTML
# =============================================================================
OUTPUT_FILE="/var/www/html/index.html"
# Create a temporary file for processing
TEMP_FILE=$(mktemp)
# Copy template to temp file
cp "$TEMPLATE_FILE" "$TEMP_FILE"
# Replace all template variables
sed -i \
-e "s|{{EXTENSION_NAME}}|$EXTENSION_DISPLAY_NAME|g" \
-e "s|{{EXTENSION_DESCRIPTION}}|$EXTENSION_DESCRIPTION|g" \
-e "s|{{EXTENSION_KEY}}|$EXTENSION_KEY|g" \
-e "s|{{DDEV_PROJECT}}|$DDEV_PROJECT|g" \
-e "s|{{COMPOSER_PACKAGE}}|$COMPOSER_PACKAGE|g" \
-e "s|{{TER_EXTENSION_KEY}}|$TER_EXTENSION_KEY|g" \
-e "s|{{GITHUB_REPO}}|$GITHUB_REPO|g" \
-e "s|{{GIT_BRANCH}}|$GIT_BRANCH|g" \
-e "s|{{GIT_COMMIT_SHORT}}|$GIT_COMMIT_SHORT|g" \
-e "s|{{GIT_COMMIT_MSG}}|$GIT_COMMIT_MSG|g" \
"$TEMP_FILE"
# Handle PR info block - remove if no PR, otherwise replace variables
if [[ -z "$PR_NUMBER" ]]; then
# Remove the PR info block entirely
sed -i '/<!-- BEGIN_PR_INFO -->/,/<!-- END_PR_INFO -->/d' "$TEMP_FILE"
else
# Replace PR variables
sed -i \
-e "s|{{PR_NUMBER}}|$PR_NUMBER|g" \
-e "s|{{PR_URL}}|$PR_URL|g" \
-e "s|{{PR_TITLE}}|$PR_TITLE|g" \
"$TEMP_FILE"
# Remove the comment markers
sed -i \
-e '/<!-- BEGIN_PR_INFO -->/d' \
-e '/<!-- END_PR_INFO -->/d' \
"$TEMP_FILE"
fi
# Move temp file to output
mv "$TEMP_FILE" "$OUTPUT_FILE"
# =============================================================================
# OUTPUT
# =============================================================================
echo ""
echo "✅ index.html generated successfully!"
echo ""
echo "📋 Configuration:"
echo " Extension: $EXTENSION_DISPLAY_NAME"
echo " Package: $COMPOSER_PACKAGE"
echo " TER Key: $TER_EXTENSION_KEY"
echo " Branch: $GIT_BRANCH"
echo " Template: $(basename "$TEMPLATE_FILE")"
echo ""
echo "🌐 Access your project at: https://$DDEV_PROJECT.ddev.site/"
#!/bin/bash
## Description: Generate Makefile for TYPO3 extension development
## Usage: generate-makefile
## Example: ddev generate-makefile
EXTENSION_KEY=$(basename "$(pwd)")
DDEV_PROJECT=$(ddev describe -j | jq -r '.name' 2>/dev/null || echo "$EXTENSION_KEY")
# Simple sed-based substitution
sed -e "s/{{EXTENSION_NAME}}/$EXTENSION_KEY/g" \
-e "s/{{EXTENSION_KEY}}/$EXTENSION_KEY/g" \
-e "s/{{DDEV_PROJECT}}/$DDEV_PROJECT/g" \
/var/www/html/.ddev/Makefile.template > /var/www/html/Makefile
echo "✅ Makefile generated successfully!"
echo "Run 'make help' to see available commands"
#!/bin/bash
## Description: Install TYPO3 Introduction Package with demo content
## Usage: install-introduction [VERSION]
## Example: ddev install-introduction
## Example: ddev install-introduction v13
VERSION=${1:-v13}
echo "Installing Introduction Package for $VERSION..."
cd /var/www/html/$VERSION
# Install introduction package
composer require typo3/cms-introduction --no-progress
# Setup extension
vendor/bin/typo3 extension:setup --extension=introduction
# Flush caches
vendor/bin/typo3 cache:flush
echo "✅ Introduction Package installed successfully!"
echo ""
echo "Demo content includes:"
echo " - 86+ pages with example content"
echo " - 226+ content elements"
echo " - Multi-language support (EN, DE, DA)"
echo " - Bootstrap Package theme"
echo ""
echo "Access your demo site at:"
echo " Frontend: https://${DDEV_SITENAME}.ddev.site/"
echo " Backend: https://${DDEV_SITENAME}.ddev.site/typo3/"
# Redis configuration for TYPO3
# Add this to your TYPO3 AdditionalConfiguration.php or settings.php
# For TYPO3 v11/v12/v13 - AdditionalConfiguration.php
# Located at: /var/www/html/v13/config/system/additional.php
<?php
# Redis Cache Configuration
$GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['pages'] = [
'backend' => \TYPO3\CMS\Core\Cache\Backend\RedisBackend::class,
'options' => [
'hostname' => 'redis',
'database' => 0,
'port' => 6379,
],
];
$GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['pagesection'] = [
'backend' => \TYPO3\CMS\Core\Cache\Backend\RedisBackend::class,
'options' => [
'hostname' => 'redis',
'database' => 1,
'port' => 6379,
],
];
$GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['rootline'] = [
'backend' => \TYPO3\CMS\Core\Cache\Backend\RedisBackend::class,
'options' => [
'hostname' => 'redis',
'database' => 2,
'port' => 6379,
],
];
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}}
- v14.{{DDEV_SITENAME}}
additional_fqdns: []
use_dns_when_possible: true
webimage_extra_dockerfiles:
- web-build
hooks:
post-start:
- exec-host: |
# Capture git info and write to container automatically on every ddev start
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
PR=$(gh pr view --json number -q .number 2>/dev/null || echo "unknown")
# Write git info as JSON to web container
ddev exec "printf '{\"branch\":\"%s\",\"commit\":\"%s\",\"pr\":\"%s\"}' '$BRANCH' '$COMMIT' '$PR' > /var/www/html/.git-info.json"
echo "📋 Git info updated: Branch=$BRANCH, Commit=$COMMIT, PR=$PR"
# Docker Compose override to pass git information to web container build
# This allows the landing page to display current branch, commit, and PR info
services:
web:
build:
args:
GIT_BRANCH: "{{GIT_BRANCH}}"
GIT_COMMIT: "{{GIT_COMMIT}}"
GIT_PR: "{{GIT_PR}}"
# Ofelia configuration for TYPO3 scheduler jobs
# This runs the TYPO3 scheduler every minute
# Uses ghcr.io/netresearch/ofelia (TYPO3-optimized fork)
services:
web:
labels:
# Run TYPO3 scheduler every minute for all versions
ofelia.job-exec.typo3-scheduler-v11.schedule: "@every 1m"
ofelia.job-exec.typo3-scheduler-v11.command: "bash -c 'cd /var/www/html/v11 && [ -f vendor/bin/typo3 ] && vendor/bin/typo3 scheduler:run || true'"
ofelia.job-exec.typo3-scheduler-v11.no-overlap: "true"
ofelia.job-exec.typo3-scheduler-v12.schedule: "@every 1m"
ofelia.job-exec.typo3-scheduler-v12.command: "bash -c 'cd /var/www/html/v12 && [ -f vendor/bin/typo3 ] && vendor/bin/typo3 scheduler:run || true'"
ofelia.job-exec.typo3-scheduler-v12.no-overlap: "true"
ofelia.job-exec.typo3-scheduler-v13.schedule: "@every 1m"
ofelia.job-exec.typo3-scheduler-v13.command: "bash -c 'cd /var/www/html/v13 && [ -f vendor/bin/typo3 ] && vendor/bin/typo3 scheduler:run || true'"
ofelia.job-exec.typo3-scheduler-v13.no-overlap: "true"
# Optional: Clear cache every hour
ofelia.job-exec.cache-warmup-v13.schedule: "@every 1h"
ofelia.job-exec.cache-warmup-v13.command: "bash -c 'cd /var/www/html/v13 && vendor/bin/typo3 cache:warmup || true'"
ofelia.job-exec.cache-warmup-v13.no-overlap: "true"
services:
# Redis 7 for TYPO3 caching (alternative to Valkey for legacy production parity)
# See docs/adr/0001-valkey-default-with-redis-alternative.md for context
#
# Use this template if:
# - Your production environment explicitly uses Redis 7.x
# - Corporate policy requires battle-tested technology only
# - You need exact production-development parity with existing infrastructure
#
# To use this instead of Valkey:
# 1. Copy this file: cp docker-compose.services-redis.yaml.optional docker-compose.services.yaml
# 2. Restart DDEV: ddev restart
redis:
container_name: ddev-${DDEV_SITENAME}-redis
image: redis:7-alpine
restart: unless-stopped
ports:
- "6379"
volumes:
- redis-data:/data
labels:
com.ddev.site-name: ${DDEV_SITENAME}
com.ddev.approot: $DDEV_APPROOT
environment:
- REDIS_MAXMEMORY=256mb
- REDIS_MAXMEMORY_POLICY=allkeys-lru
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
# MailPit for email testing
mailpit:
container_name: ddev-${DDEV_SITENAME}-mailpit
image: axllent/mailpit:latest
restart: unless-stopped
ports:
- "1025" # SMTP port
- "8025" # Web UI port
labels:
com.ddev.site-name: ${DDEV_SITENAME}
com.ddev.approot: $DDEV_SITENAME
environment:
MP_SMTP_BIND_ADDR: 0.0.0.0:1025
MP_UI_BIND_ADDR: 0.0.0.0:8025
# Ofelia for cron jobs (TYPO3 scheduler)
ofelia:
container_name: ddev-${DDEV_SITENAME}-ofelia
image: ghcr.io/netresearch/ofelia:latest
restart: unless-stopped
depends_on:
- web
labels:
com.ddev.site-name: ${DDEV_SITENAME}
com.ddev.approot: $DDEV_APPROOT
ofelia.enabled: "true"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- .:/src
command: daemon --docker-events
volumes:
redis-data:
name: "${DDEV_SITENAME}-redis-data"
services:
# Valkey for TYPO3 caching (open source, wire-compatible with Redis)
# See docs/adr/0001-valkey-default-with-redis-alternative.md for rationale
# For Redis 7 alternative, see templates/docker-compose.services-redis.yaml.optional
valkey:
container_name: ddev-${DDEV_SITENAME}-valkey
image: valkey/valkey:8-alpine
restart: unless-stopped
ports:
- "6379"
volumes:
- valkey-data:/data
labels:
com.ddev.site-name: ${DDEV_SITENAME}
com.ddev.approot: $DDEV_APPROOT
environment:
- VALKEY_MAXMEMORY=256mb
- VALKEY_MAXMEMORY_POLICY=allkeys-lru
command: valkey-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
# MailPit for email testing
mailpit:
container_name: ddev-${DDEV_SITENAME}-mailpit
image: axllent/mailpit:latest
restart: unless-stopped
ports:
- "1025" # SMTP port
- "8025" # Web UI port
labels:
com.ddev.site-name: ${DDEV_SITENAME}
com.ddev.approot: $DDEV_SITENAME
environment:
MP_SMTP_BIND_ADDR: 0.0.0.0:1025
MP_UI_BIND_ADDR: 0.0.0.0:8025
# Ofelia for cron jobs (TYPO3 scheduler)
ofelia:
container_name: ddev-${DDEV_SITENAME}-ofelia
image: ghcr.io/netresearch/ofelia:latest
restart: unless-stopped
depends_on:
- web
labels:
com.ddev.site-name: ${DDEV_SITENAME}
com.ddev.approot: $DDEV_APPROOT
ofelia.enabled: "true"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- .:/src
command: daemon --docker-events
volumes:
valkey-data:
name: "${DDEV_SITENAME}-valkey-data"
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=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=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
- v14-data:/var/www/html/v14
volumes:
v11-data:
name: "${DDEV_SITENAME}-v11-data"
v12-data:
name: "${DDEV_SITENAME}-v12-data"
v13-data:
name: "${DDEV_SITENAME}-v13-data"
v14-data:
name: "${DDEV_SITENAME}-v14-data"
# TYPO3 Scheduler Aliases
alias t3-scheduler-v11='cd /var/www/html/v11 && vendor/bin/typo3 scheduler:run'
alias t3-scheduler-v12='cd /var/www/html/v12 && vendor/bin/typo3 scheduler:run'
alias t3-scheduler-v13='cd /var/www/html/v13 && vendor/bin/typo3 scheduler:run'
alias t3-scheduler-all='t3-scheduler-v11 && t3-scheduler-v12 && t3-scheduler-v13'
# Redis CLI access
alias redis='redis-cli -h redis'
# Cache management
alias t3-cache-flush-v13='cd /var/www/html/v13 && vendor/bin/typo3 cache:flush'
alias t3-cache-warmup-v13='cd /var/www/html/v13 && vendor/bin/typo3 cache:warmup'
# Quick TYPO3 commands
alias t3='cd /var/www/html/v13 && vendor/bin/typo3'
<!DOCTYPE html>
<html lang="en" data-theme="system">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{EXTENSION_NAME}} - DDEV Development Environment</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🚀</text></svg>">
<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@300;400;500;600;700;800&family=Open+Sans:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
/* ═══════════════════════════════════════════════════════════════
NETRESEARCH DDEV LANDING PAGE TEMPLATE
Template Variables:
- {{EXTENSION_NAME}} : Display name of the extension
- {{EXTENSION_DESCRIPTION}} : Short description
- {{DDEV_PROJECT}} : DDEV project identifier (used in URLs)
- {{GITHUB_REPO}} : Full GitHub repository URL
- {{GIT_BRANCH}} : Current git branch name
- {{GIT_COMMIT_SHORT}} : Short commit hash (7 chars)
- {{GIT_COMMIT_MSG}} : Last commit message (first line)
- {{PR_NUMBER}} : Pull request number (empty if none)
- {{PR_TITLE}} : Pull request title (empty if none)
- {{PR_URL}} : Pull request URL (empty if none)
═══════════════════════════════════════════════════════════════ */
/* ═══════════════════════════════════════
THEME VARIABLES
═══════════════════════════════════════ */
:root {
/* Netresearch Brand Colors (constant) */
--nr-turquoise: #2F99A4;
--nr-turquoise-light: #3db5c2;
--nr-turquoise-dark: #247a83;
--nr-orange: #FF4D00;
--nr-anthracite: #585961;
/* Semantic Colors */
--color-v12: #22c55e;
--color-v12-bg: rgba(34, 197, 94, 0.1);
--color-v13: #3b82f6;
--color-v13-bg: rgba(59, 130, 246, 0.1);
--color-v14: #a855f7;
--color-v14-bg: rgba(168, 85, 247, 0.1);
/* Spacing */
--space-xs: 0.25rem;
--space-sm: 0.5rem;
--space-md: 1rem;
--space-lg: 1.5rem;
--space-xl: 2rem;
--space-2xl: 3rem;
--space-3xl: 4rem;
/* Typography */
--font-display: 'Raleway', sans-serif;
--font-body: 'Open Sans', sans-serif;
--font-mono: 'JetBrains Mono', monospace;
/* Transitions */
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
--ease-out-back: cubic-bezier(0.34, 1.56, 0.64, 1);
--theme-transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
}
/* Light Theme (default) */
:root, [data-theme="light"] {
--page-bg: #e8eaed;
--header-bg: var(--nr-turquoise);
--card-bg: #FFFFFF;
--card-bg-alt: #f7f8f9;
--text-primary: #3d3e44;
--text-secondary: #6e6f78;
--border-color: #CCCDCC;
--border-color-light: rgba(0, 0, 0, 0.06);
--shadow-color: rgba(0, 0, 0, 0.08);
--terminal-bg: #3d3e44;
--terminal-text: rgba(255, 255, 255, 0.9);
--terminal-comment: rgba(255, 255, 255, 0.6);
--footer-bg: var(--nr-anthracite);
--footer-text: rgba(255, 255, 255, 0.7);
}
/* Dark Theme */
[data-theme="dark"] {
--page-bg: #1a1a1a;
--header-bg: #252525;
--card-bg: #2d2d2d;
--card-bg-alt: #252525;
--text-primary: #e5e5e5;
--text-secondary: #a0a0a0;
--border-color: #404040;
--border-color-light: rgba(255, 255, 255, 0.06);
--shadow-color: rgba(0, 0, 0, 0.3);
--terminal-bg: #1a1a1a;
--terminal-text: rgba(255, 255, 255, 0.9);
--terminal-comment: rgba(255, 255, 255, 0.5);
--footer-bg: #1a1a1a;
--footer-text: rgba(255, 255, 255, 0.6);
}
/* System Theme - follows OS preference */
@media (prefers-color-scheme: dark) {
[data-theme="system"] {
--page-bg: #1a1a1a;
--header-bg: #252525;
--card-bg: #2d2d2d;
--card-bg-alt: #252525;
--text-primary: #e5e5e5;
--text-secondary: #a0a0a0;
--border-color: #404040;
--border-color-light: rgba(255, 255, 255, 0.06);
--shadow-color: rgba(0, 0, 0, 0.3);
--terminal-bg: #1a1a1a;
--terminal-text: rgba(255, 255, 255, 0.9);
--terminal-comment: rgba(255, 255, 255, 0.5);
--footer-bg: #1a1a1a;
--footer-text: rgba(255, 255, 255, 0.6);
}
}
/* Reset & Base */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
scroll-behavior: smooth;
}
/* Focus States for Accessibility */
:focus-visible {
outline: 2px solid var(--nr-turquoise);
outline-offset: 2px;
}
body {
font-family: var(--font-body);
color: var(--text-primary);
background: var(--page-bg);
line-height: 1.6;
min-height: 100vh;
overflow-x: hidden;
transition: var(--theme-transition);
}
/* Main Container */
.page-wrapper {
max-width: 1280px;
margin: 0 auto;
padding: var(--space-xl);
}
/* Main Card Container */
.main-card {
background: var(--card-bg);
border-radius: 16px;
box-shadow: 0 4px 24px var(--shadow-color);
overflow: hidden;
transition: var(--theme-transition);
}
/* ═══════════════════════════════════════
THEME SWITCHER
═══════════════════════════════════════ */
.theme-switcher {
position: absolute;
top: var(--space-lg);
right: var(--space-lg);
display: flex;
align-items: center;
gap: var(--space-xs);
background: var(--card-bg-alt);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 4px;
z-index: 10;
}
.theme-btn {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border: none;
background: transparent;
border-radius: 6px;
cursor: pointer;
color: var(--text-secondary);
font-size: 1rem;
transition: all 0.2s ease;
}
.theme-btn:hover {
background: var(--border-color);
color: var(--text-primary);
}
.theme-btn.active {
background: var(--nr-turquoise);
color: white;
}
.theme-btn svg {
width: 18px;
height: 18px;
}
/* ═══════════════════════════════════════
HEADER SECTION
═══════════════════════════════════════ */
.header {
position: relative;
padding: var(--space-3xl) var(--space-2xl) var(--space-2xl);
background: var(--card-bg);
border-bottom: 1px solid var(--border-color);
border-top: 4px solid var(--nr-turquoise);
transition: var(--theme-transition);
}
.header-content {
display: flex;
align-items: flex-start;
gap: var(--space-xl);
}
.logo-container {
flex-shrink: 0;
width: 80px;
height: 80px;
}
.logo-container svg {
width: 100%;
height: 100%;
}
.header-text {
flex: 1;
}
.header-title {
font-family: var(--font-display);
font-size: clamp(1.75rem, 4vw, 2.5rem);
font-weight: 700;
color: var(--text-primary);
letter-spacing: -0.02em;
line-height: 1.2;
margin-bottom: var(--space-sm);
}
.header-description {
font-size: 1.1rem;
color: var(--text-secondary);
margin-bottom: var(--space-sm);
max-width: 600px;
}
.header-package {
display: inline-block;
font-family: 'SF Mono', 'Consolas', 'Monaco', monospace;
font-size: 0.9rem;
color: var(--text-secondary);
background: var(--page-bg);
padding: var(--space-xs) var(--space-md);
border-radius: var(--radius-sm);
border: 1px solid var(--border-color);
margin-bottom: var(--space-lg);
}
.badges {
display: flex;
flex-wrap: wrap;
gap: var(--space-sm);
}
.badge {
display: inline-flex;
align-items: center;
gap: var(--space-xs);
padding: var(--space-xs) var(--space-md);
background: var(--nr-turquoise);
color: white;
font-family: var(--font-display);
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
border-radius: 100px;
}
.badge--secondary {
background: var(--nr-anthracite);
}
.badge--outline {
background: transparent;
border: 1.5px solid var(--nr-turquoise);
color: var(--nr-turquoise);
}
/* Git Info */
.git-info {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--space-md);
margin-top: var(--space-lg);
padding-top: var(--space-lg);
border-top: 1px solid var(--border-color-light);
}
.git-item {
display: flex;
align-items: center;
gap: var(--space-sm);
font-size: 0.85rem;
color: var(--text-secondary);
}
.git-branch {
display: inline-flex;
padding: var(--space-xs) var(--space-sm);
background: var(--nr-turquoise);
color: white;
font-family: var(--font-mono);
font-size: 0.75rem;
font-weight: 500;
border-radius: 4px;
}
.git-commit {
font-family: var(--font-mono);
font-size: 0.8rem;
color: var(--nr-turquoise);
background: rgba(47, 153, 164, 0.15);
padding: 2px 8px;
border-radius: 4px;
}
.git-commit-msg {
font-size: 0.8rem;
color: var(--text-secondary);
font-style: italic;
max-width: 300px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.pr-link {
display: inline-flex;
align-items: center;
gap: var(--space-xs);
padding: var(--space-xs) var(--space-sm);
background: var(--nr-turquoise);
color: white;
font-family: var(--font-display);
font-size: 0.75rem;
font-weight: 600;
text-decoration: none;
border-radius: 6px;
transition: all 0.2s ease;
}
.pr-link:hover {
background: var(--nr-turquoise-dark);
}
.pr-title {
font-size: 0.8rem;
color: var(--text-secondary);
max-width: 250px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* ═══════════════════════════════════════
CONTENT SECTIONS
═══════════════════════════════════════ */
.content {
padding: var(--space-2xl);
}
.section {
margin-bottom: var(--space-2xl);
}
.section:last-child {
margin-bottom: 0;
}
.section-header {
display: flex;
align-items: center;
gap: var(--space-md);
margin-bottom: var(--space-lg);
}
.section-icon {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(47, 153, 164, 0.15);
border-radius: 8px;
font-size: 1rem;
}
.section-title {
font-family: var(--font-display);
font-size: 1.25rem;
font-weight: 700;
color: var(--text-primary);
}
/* ═══════════════════════════════════════
TYPO3 INSTANCES GRID
═══════════════════════════════════════ */
.instances-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: var(--space-lg);
}
.instance-card {
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: var(--space-xl);
transition: all 0.3s ease;
}
.instance-card:hover {
border-color: var(--nr-turquoise);
box-shadow: 0 8px 24px var(--shadow-color);
}
.instance-card--v12 { --card-accent: var(--color-v12); }
.instance-card--v13 { --card-accent: var(--color-v13); }
.instance-card--v14 { --card-accent: var(--color-v14); }
.instance-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--space-md);
}
.instance-version {
font-family: var(--font-display);
font-size: 1.125rem;
font-weight: 700;
color: var(--text-primary);
}
.instance-badge {
padding: var(--space-xs) var(--space-sm);
background: var(--color-v12-bg);
color: var(--card-accent, var(--nr-turquoise));
font-family: var(--font-mono);
font-size: 0.7rem;
font-weight: 500;
border-radius: 6px;
text-transform: uppercase;
}
.instance-description {
font-size: 0.9rem;
color: var(--text-secondary);
margin-bottom: var(--space-lg);
}
.instance-links {
display: flex;
gap: var(--space-sm);
}
.instance-link {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: var(--space-sm) var(--space-md);
font-family: var(--font-display);
font-size: 0.85rem;
font-weight: 600;
text-decoration: none;
border-radius: 8px;
transition: all 0.2s ease;
}
.instance-link--primary {
background: var(--nr-turquoise);
color: white;
}
.instance-link--primary:hover {
background: var(--nr-turquoise-dark);
}
.instance-link--secondary {
background: transparent;
color: var(--nr-turquoise);
border: 1.5px solid var(--nr-turquoise);
}
.instance-link--secondary:hover {
background: var(--nr-turquoise);
color: white;
}
/* ═══════════════════════════════════════
CREDENTIALS BOX
═══════════════════════════════════════ */
.credentials-box {
background: var(--card-bg-alt);
border: 1px solid var(--border-color);
border-left: 4px solid var(--nr-orange);
border-radius: 12px;
padding: var(--space-lg);
transition: var(--theme-transition);
}
.credentials-header {
display: flex;
align-items: center;
gap: var(--space-sm);
margin-bottom: var(--space-md);
}
.credentials-title {
font-family: var(--font-display);
font-size: 1rem;
font-weight: 700;
color: var(--text-primary);
}
.credentials-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: var(--space-lg);
}
.credential-item {
display: flex;
flex-direction: column;
gap: var(--space-xs);
}
.credential-label {
font-size: 0.75rem;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.credential-value {
font-family: var(--font-mono);
font-size: 0.95rem;
font-weight: 500;
color: var(--text-primary);
background: var(--card-bg);
padding: var(--space-sm) var(--space-md);
border-radius: 6px;
border: 1px solid var(--border-color);
user-select: all;
cursor: pointer;
transition: all 0.2s ease;
}
.credential-value:hover {
border-color: var(--nr-turquoise);
}
/* ═══════════════════════════════════════
TOOLS GRID
═══════════════════════════════════════ */
.tools-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: var(--space-md);
}
.tool-card {
display: flex;
align-items: center;
gap: var(--space-md);
padding: var(--space-lg);
background: var(--card-bg-alt);
border: 1px solid transparent;
border-radius: 12px;
text-decoration: none;
color: inherit;
transition: all 0.2s ease;
}
.tool-card:hover {
background: var(--card-bg);
border-color: var(--nr-turquoise);
}
.tool-icon {
width: 44px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
background: var(--card-bg);
border-radius: 10px;
font-size: 1.25rem;
flex-shrink: 0;
}
.tool-info {
flex: 1;
min-width: 0;
}
.tool-name {
font-family: var(--font-display);
font-size: 0.95rem;
font-weight: 600;
color: var(--text-primary);
}
.tool-description {
font-size: 0.8rem;
color: var(--text-secondary);
}
/* ═══════════════════════════════════════
COMMANDS SECTION
═══════════════════════════════════════ */
.commands-terminal {
background: var(--terminal-bg);
border-radius: 12px;
overflow: hidden;
transition: var(--theme-transition);
}
.terminal-header {
padding: var(--space-sm) var(--space-lg);
background: rgba(0, 0, 0, 0.2);
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.terminal-title {
font-family: var(--font-mono);
font-size: 0.75rem;
color: rgba(255, 255, 255, 0.5);
}
.terminal-body {
padding: var(--space-lg);
font-family: var(--font-mono);
font-size: 0.85rem;
line-height: 1.8;
}
.command-line {
display: flex;
gap: var(--space-sm);
padding: var(--space-sm) 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.03);
}
.command-line:last-child {
border-bottom: none;
}
.command-prompt {
color: var(--nr-turquoise-light);
user-select: none;
}
.command-text {
color: var(--terminal-text);
flex: 1;
}
.command-comment {
color: var(--terminal-comment);
font-style: italic;
}
/* ═══════════════════════════════════════
FOOTER
═══════════════════════════════════════ */
.footer {
padding: var(--space-xl) var(--space-2xl);
background: var(--footer-bg);
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: var(--space-md);
transition: var(--theme-transition);
}
.footer-brand {
display: flex;
align-items: center;
gap: var(--space-sm);
}
.footer-logo {
width: 32px;
height: 32px;
opacity: 0.9;
}
.footer-text {
font-size: 0.85rem;
color: var(--footer-text);
}
.footer-link {
color: var(--nr-turquoise-light);
text-decoration: none;
font-weight: 500;
}
.footer-link:hover {
color: white;
}
.footer-meta {
display: flex;
align-items: center;
gap: var(--space-md);
font-size: 0.8rem;
color: var(--footer-text);
}
.footer-divider {
width: 4px;
height: 4px;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
}
/* ═══════════════════════════════════════
RESPONSIVE DESIGN
═══════════════════════════════════════ */
@media (max-width: 768px) {
.page-wrapper {
padding: var(--space-md);
}
.header {
padding: var(--space-xl) var(--space-lg);
}
.header-content {
flex-direction: column;
align-items: center;
text-align: center;
}
.badges {
justify-content: center;
}
.git-info {
flex-direction: column;
align-items: center;
text-align: center;
}
.theme-switcher {
position: relative;
top: 0;
right: 0;
margin-bottom: var(--space-lg);
align-self: center;
}
.content {
padding: var(--space-lg);
}
.instances-grid,
.tools-grid,
.credentials-grid {
grid-template-columns: 1fr;
}
.footer {
flex-direction: column;
text-align: center;
}
.footer-meta {
flex-direction: column;
gap: var(--space-xs);
}
.footer-divider {
display: none;
}
}
@media (max-width: 480px) {
.instance-links {
flex-direction: column;
}
.terminal-body {
font-size: 0.75rem;
}
.command-comment {
display: none;
}
}
/* Print Styles */
@media print {
body {
background: white;
}
.main-card {
box-shadow: none;
}
.theme-switcher {
display: none;
}
}
</style>
</head>
<body>
<div class="page-wrapper">
<div class="main-card">
<!-- ═══════════════════════════════════════
HEADER
═══════════════════════════════════════ -->
<header class="header">
<!-- Theme Switcher -->
<div class="theme-switcher" role="group" aria-label="Theme selection">
<button class="theme-btn" data-theme="light" title="Light mode" aria-label="Light mode">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="5"/>
<path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/>
</svg>
</button>
<button class="theme-btn" data-theme="system" title="System preference" aria-label="System preference">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="2" y="3" width="20" height="14" rx="2"/>
<path d="M8 21h8M12 17v4"/>
</svg>
</button>
<button class="theme-btn" data-theme="dark" title="Dark mode" aria-label="Dark mode">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
</svg>
</button>
</div>
<div class="header-content">
<div class="logo-container">
<!-- Netresearch Official Logo SVG -->
<svg viewBox="-75 -75 440 440" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Netresearch Logo">
<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>
</div>
<div class="header-text">
<h1 class="header-title">{{EXTENSION_NAME}}</h1>
<p class="header-description">{{EXTENSION_DESCRIPTION}}</p>
<code class="header-package">{{COMPOSER_PACKAGE}}</code>
<!-- Git & PR Info -->
<div class="git-info">
<div class="git-item">
<span>🌿</span>
<span class="git-branch">{{GIT_BRANCH}}</span>
</div>
<div class="git-item">
<span>📝</span>
<span class="git-commit">{{GIT_COMMIT_SHORT}}</span>
<span class="git-commit-msg">{{GIT_COMMIT_MSG}}</span>
</div>
<!-- PR info (only shown when PR_NUMBER is set) -->
<!-- BEGIN_PR_INFO -->
<div class="git-item">
<a href="{{PR_URL}}" target="_blank" rel="noopener" class="pr-link">
🔀 PR #{{PR_NUMBER}}
</a>
<span class="pr-title">{{PR_TITLE}}</span>
</div>
<!-- END_PR_INFO -->
</div>
</div>
</div>
</header>
<main class="content">
<!-- ═══════════════════════════════════════
TYPO3 INSTANCES
═══════════════════════════════════════ -->
<section class="section">
<div class="section-header">
<div class="section-icon">🚀</div>
<h2 class="section-title">TYPO3 Instances</h2>
</div>
<div class="instances-grid">
<!-- TYPO3 v12 -->
<div class="instance-card instance-card--v12">
<div class="instance-header">
<span class="instance-version">TYPO3 v12</span>
<span class="instance-badge">LTS</span>
</div>
<p class="instance-description">TYPO3 12.4 LTS with extension installed and configured for development.</p>
<div class="instance-links">
<a href="https://v12.{{DDEV_PROJECT}}.ddev.site/typo3" target="_blank" rel="noopener" class="instance-link instance-link--primary">Backend</a>
<a href="https://v12.{{DDEV_PROJECT}}.ddev.site/" target="_blank" rel="noopener" class="instance-link instance-link--secondary">Frontend</a>
</div>
</div>
<!-- TYPO3 v13 -->
<div class="instance-card instance-card--v13">
<div class="instance-header">
<span class="instance-version">TYPO3 v13</span>
<span class="instance-badge">LTS</span>
</div>
<p class="instance-description">TYPO3 13.4 LTS with extension installed and configured for development.</p>
<div class="instance-links">
<a href="https://v13.{{DDEV_PROJECT}}.ddev.site/typo3" target="_blank" rel="noopener" class="instance-link instance-link--primary">Backend</a>
<a href="https://v13.{{DDEV_PROJECT}}.ddev.site/" target="_blank" rel="noopener" class="instance-link instance-link--secondary">Frontend</a>
</div>
</div>
</div>
</section>
<!-- ═══════════════════════════════════════
CREDENTIALS
═══════════════════════════════════════ -->
<section class="section">
<div class="credentials-box">
<div class="credentials-header">
<span>🔑</span>
<h3 class="credentials-title">Backend Credentials</h3>
</div>
<div class="credentials-grid">
<div class="credential-item">
<span class="credential-label">Username</span>
<span class="credential-value" title="Click to select">admin</span>
</div>
<div class="credential-item">
<span class="credential-label">Password</span>
<span class="credential-value" title="Click to select">Joh316!</span>
</div>
</div>
</div>
</section>
<!-- ═══════════════════════════════════════
DEVELOPMENT TOOLS
═══════════════════════════════════════ -->
<section class="section">
<div class="section-header">
<div class="section-icon">🛠️</div>
<h2 class="section-title">Development Tools</h2>
</div>
<div class="tools-grid">
<a href="https://docs.{{DDEV_PROJECT}}.ddev.site/" target="_blank" rel="noopener" class="tool-card">
<div class="tool-icon">📚</div>
<div class="tool-info">
<div class="tool-name">Documentation</div>
<div class="tool-description">Rendered RST docs</div>
</div>
</a>
<a href="https://{{DDEV_PROJECT}}.ddev.site:8026/" target="_blank" rel="noopener" class="tool-card">
<div class="tool-icon">📧</div>
<div class="tool-info">
<div class="tool-name">Mailpit</div>
<div class="tool-description">Email testing</div>
</div>
</a>
<a href="{{GITHUB_REPO}}" target="_blank" rel="noopener" class="tool-card">
<div class="tool-icon">📖</div>
<div class="tool-info">
<div class="tool-name">GitHub</div>
<div class="tool-description">Source code</div>
</div>
</a>
<a href="https://extensions.typo3.org/extension/{{TER_EXTENSION_KEY}}" target="_blank" rel="noopener" class="tool-card">
<div class="tool-icon">🧩</div>
<div class="tool-info">
<div class="tool-name">TER</div>
<div class="tool-description">TYPO3 Extension Repository</div>
</div>
</a>
<a href="https://packagist.org/packages/{{COMPOSER_PACKAGE}}" target="_blank" rel="noopener" class="tool-card">
<div class="tool-icon">📦</div>
<div class="tool-info">
<div class="tool-name">Packagist</div>
<div class="tool-description">Composer package</div>
</div>
</a>
</div>
</section>
<!-- ═══════════════════════════════════════
QUICK COMMANDS
═══════════════════════════════════════ -->
<section class="section">
<div class="section-header">
<div class="section-icon">⌨️</div>
<h2 class="section-title">Quick Commands</h2>
</div>
<div class="commands-terminal">
<div class="terminal-header">
<span class="terminal-title">$ {{DDEV_PROJECT}}</span>
</div>
<div class="terminal-body">
<div class="command-line">
<span class="command-prompt">$</span>
<span class="command-text">make help</span>
<span class="command-comment"># Show all available targets</span>
</div>
<div class="command-line">
<span class="command-prompt">$</span>
<span class="command-text">make test</span>
<span class="command-comment"># Run all tests (unit + functional)</span>
</div>
<div class="command-line">
<span class="command-prompt">$</span>
<span class="command-text">make lint</span>
<span class="command-comment"># PHPStan, CS-Fixer, Rector check</span>
</div>
<div class="command-line">
<span class="command-prompt">$</span>
<span class="command-text">make fix</span>
<span class="command-comment"># Auto-fix code style issues</span>
</div>
<div class="command-line">
<span class="command-prompt">$</span>
<span class="command-text">make docs</span>
<span class="command-comment"># Render documentation locally</span>
</div>
<div class="command-line">
<span class="command-prompt">$</span>
<span class="command-text">make clean</span>
<span class="command-comment"># Clean build artifacts</span>
</div>
</div>
</div>
</section>
</main>
<!-- ═══════════════════════════════════════
FOOTER
═══════════════════════════════════════ -->
<footer class="footer">
<div class="footer-brand">
<svg class="footer-logo" viewBox="-75 -75 440 440" xmlns="http://www.w3.org/2000/svg">
<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="#ffffff" 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>
<span class="footer-text">
Developed by <a href="https://www.netresearch.de/" target="_blank" rel="noopener" class="footer-link">Netresearch DTT GmbH</a>
</span>
</div>
<div class="footer-meta">
<span>DDEV Development Environment</span>
<span class="footer-divider"></span>
<span>PHP 8.2+</span>
<span class="footer-divider"></span>
<span>TYPO3 12.4 – 13.4 LTS</span>
</div>
</footer>
</div>
</div>
<script>
// Theme Switcher
(function() {
const STORAGE_KEY = 'ddev-theme';
const buttons = document.querySelectorAll('.theme-btn');
const html = document.documentElement;
function setTheme(theme) {
html.setAttribute('data-theme', theme);
localStorage.setItem(STORAGE_KEY, theme);
buttons.forEach(btn => {
btn.classList.toggle('active', btn.dataset.theme === theme);
});
}
// Initialize from storage or default to system
const savedTheme = localStorage.getItem(STORAGE_KEY) || 'system';
setTheme(savedTheme);
// Add click handlers
buttons.forEach(btn => {
btn.addEventListener('click', () => setTheme(btn.dataset.theme));
});
})();
</script>
</body>
</html>
# 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.4 LTS with extension
ddev install-v13
.PHONY: install-v14
install-v14: ## Install TYPO3 v14.3 LTS with extension (default / gold standard)
ddev install-v14
.PHONY: install-all
install-all: ## Install all TYPO3 versions (v11 + v12 + v13 + v14)
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 v14.3 LTS (default / gold standard):"
@echo " Frontend: https://v14.{{DDEV_PROJECT}}.ddev.site/"
@echo " Backend: https://v14.{{DDEV_PROJECT}}.ddev.site/typo3/"
@echo ""
@echo "TYPO3 v13.4 LTS:"
@echo " Frontend: https://v13.{{DDEV_PROJECT}}.ddev.site/"
@echo " Backend: https://v13.{{DDEV_PROJECT}}.ddev.site/typo3/"
@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 "Backend Credentials:"
@echo " Username: admin"
@echo " Password: Joh316!!"
@echo ""
.DEFAULT_GOAL := help
# 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 / 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/
ARG BASE_IMAGE
FROM $BASE_IMAGE
ENV EXTENSION_KEY "{{EXTENSION_KEY}}"
ENV DDEV_SITENAME "{{DDEV_SITENAME}}"
ENV GITHUB_URL "{{GITHUB_URL}}"
# Remove default Apache index.html and create PHP-based landing page
RUN rm -f /var/www/html/index.html
RUN cat > /var/www/html/index.php << 'PHPEOF'
<?php
// Get git info from runtime file (updated automatically on each ddev start)
$gitInfoFile = '/var/www/html/.git-info.json';
$gitInfo = file_exists($gitInfoFile) ? json_decode(file_get_contents($gitInfoFile), true) : [];
$gitBranch = $gitInfo['branch'] ?? 'unknown';
$gitCommit = $gitInfo['commit'] ?? 'unknown';
$prNumber = isset($gitInfo['pr']) && $gitInfo['pr'] !== 'unknown' ? $gitInfo['pr'] : null;
$githubUrl = '{{GITHUB_URL}}';
$branchUrl = $githubUrl && $gitBranch !== 'unknown' ? $githubUrl . '/tree/' . urlencode($gitBranch) : null;
$prUrl = $githubUrl && $prNumber ? $githubUrl . '/pull/' . $prNumber : null;
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>EXT:{{EXTENSION_KEY}} - DDEV Environment</title>
<style>
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,sans-serif;max-width:900px;margin:50px auto;padding:0 20px;line-height:1.6;color:#333;background:#f5f5f5}
.container{background:#fff;padding:40px;border-radius:8px;box-shadow:0 2px 10px rgba(0,0,0,0.1)}
h1{color:#ff8700;border-bottom:3px solid #ff8700;padding-bottom:10px;margin-bottom:10px}
.git-info{font-size:0.85em;color:#666;margin-bottom:30px;padding:10px;background:#f9f9f9;border-radius:4px}
.git-info a{color:#0078e7;text-decoration:none;font-weight:500}
.git-info a:hover{text-decoration:underline}
.git-info .separator{margin:0 8px;color:#ccc}
h2{color:#666;margin-top:30px;margin-bottom:15px;font-size:1.3em}
.card{background:#f9f9f9;border-left:4px solid #ff8700;padding:20px;margin:15px 0;border-radius:4px}
.card h3{margin-top:0;color:#ff8700}
a{color:#0078e7;text-decoration:none;font-weight:500}
a:hover{text-decoration:underline}
code{background:#eee;padding:2px 6px;border-radius:3px;font-family:monospace;font-size:0.9em}
.links{display:flex;gap:15px;margin-top:10px;flex-wrap:wrap}
.btn{display:inline-block;padding:10px 20px;background:#0078e7;color:#fff!important;border-radius:4px;text-decoration:none;transition:background 0.3s}
.btn:hover{background:#0056b3;text-decoration:none}
.btn-docs{background:#ff8700}
.btn-docs:hover{background:#e67700}
ul{list-style:none;padding:0}
li{padding:5px 0}
.version-card{margin:10px 0}
</style>
</head>
<body>
<div class="container">
<h1>🚀 EXT:{{EXTENSION_KEY}} Development Environment</h1>
<?php if ($gitBranch !== 'unknown' || $prNumber): ?>
<div class="git-info">
<?php if ($branchUrl): ?>
<a href="<?= htmlspecialchars($branchUrl) ?>" target="_blank" title="View branch on GitHub">
🌿 <?= htmlspecialchars($gitBranch) ?><?php if ($gitCommit !== 'unknown'): ?> @ <?= htmlspecialchars($gitCommit) ?><?php endif; ?>
</a>
<?php elseif ($gitBranch !== 'unknown'): ?>
🌿 <?= htmlspecialchars($gitBranch) ?><?php if ($gitCommit !== 'unknown'): ?> @ <?= htmlspecialchars($gitCommit) ?><?php endif; ?>
<?php endif; ?>
<?php if ($prUrl): ?>
<span class="separator">|</span>
<a href="<?= htmlspecialchars($prUrl) ?>" target="_blank" title="View pull request">
🔀 PR #<?= htmlspecialchars($prNumber) ?>
</a>
<?php endif; ?>
</div>
<?php endif; ?>
<div class="card">
<h3>📚 Documentation</h3>
<p>View the extension documentation (run <code>ddev docs</code> to render):</p>
<a href="https://docs.{{DDEV_SITENAME}}.ddev.site/" class="btn btn-docs">View Documentation</a>
</div>
<div class="card">
<h3>🌐 TYPO3 Environments</h3>
<div class="version-card">
<strong>TYPO3 11.5 LTS</strong>
<div class="links">
<a href="https://v11.{{DDEV_SITENAME}}.ddev.site/" class="btn">Frontend</a>
<a href="https://v11.{{DDEV_SITENAME}}.ddev.site/typo3/" class="btn">Backend</a>
</div>
</div>
<div class="version-card">
<strong>TYPO3 12.4 LTS</strong>
<div class="links">
<a href="https://v12.{{DDEV_SITENAME}}.ddev.site/" class="btn">Frontend</a>
<a href="https://v12.{{DDEV_SITENAME}}.ddev.site/typo3/" class="btn">Backend</a>
</div>
</div>
<div class="version-card">
<strong>TYPO3 13.4 LTS</strong>
<div class="links">
<a href="https://v13.{{DDEV_SITENAME}}.ddev.site/" class="btn">Frontend</a>
<a href="https://v13.{{DDEV_SITENAME}}.ddev.site/typo3/" class="btn">Backend</a>
</div>
</div>
</div>
<div class="card">
<h3>🔑 Backend Credentials</h3>
<ul>
<li><strong>Username:</strong> <code>admin</code></li>
<li><strong>Password:</strong> <code>Joh316!!</code></li>
<li><em>(also works for Install Tool)</em></li>
</ul>
</div>
<div class="card">
<h3>⚙️ Quick Commands</h3>
<ul>
<li><code>ddev setup</code> - <strong>Complete setup (all-in-one)</strong></li>
<li><code>ddev docs</code> - Render documentation</li>
<li><code>ddev install-v11</code> - Install TYPO3 11.5 LTS</li>
<li><code>ddev install-v12</code> - Install TYPO3 12.4 LTS</li>
<li><code>ddev install-v13</code> - Install TYPO3 13.4 LTS</li>
<li><code>ddev install-all</code> - Install all TYPO3 versions</li>
</ul>
</div>
</div>
</body>
</html>
PHPEOF
RUN mkdir -p /var/www/html/v11/public/typo3
RUN echo "<h1>Perform this first</h1> <code>ddev install-v11</code>" > /var/www/html/v11/public/index.html
RUN echo "<h1>Perform this first</h1> <code>ddev install-v11</code>" > /var/www/html/v11/public/typo3/index.html
RUN mkdir -p /var/www/html/v12/public/typo3
RUN echo "<h1>Perform this first</h1> <code>ddev install-v12</code>" > /var/www/html/v12/public/index.html
RUN echo "<h1>Perform this first</h1> <code>ddev install-v12</code>" > /var/www/html/v12/public/typo3/index.html
RUN mkdir -p /var/www/html/v13/public/typo3
RUN echo "<h1>Perform this first</h1> <code>ddev install-v13</code>" > /var/www/html/v13/public/index.html
RUN echo "<h1>Perform this first</h1> <code>ddev install-v13</code>" > /var/www/html/v13/public/typo3/index.html
ARG uid
ARG gid
RUN chown -R $uid:$gid /var/www/html
#!/bin/bash
# Install cron in web container (alternative to Ofelia)
# This script sets up traditional cron jobs in the web container
cat > /etc/cron.d/typo3-scheduler << 'EOF'
# TYPO3 Scheduler - runs every minute for all versions
* * * * * www-data cd /var/www/html/v11 && [ -f vendor/bin/typo3 ] && vendor/bin/typo3 scheduler:run > /dev/null 2>&1
* * * * * www-data cd /var/www/html/v12 && [ -f vendor/bin/typo3 ] && vendor/bin/typo3 scheduler:run > /dev/null 2>&1
* * * * * www-data cd /var/www/html/v13 && [ -f vendor/bin/typo3 ] && vendor/bin/typo3 scheduler:run > /dev/null 2>&1
# Optional: Cache warmup every hour
0 * * * * www-data cd /var/www/html/v13 && vendor/bin/typo3 cache:warmup > /dev/null 2>&1
EOF
chmod 0644 /etc/cron.d/typo3-scheduler
crontab /etc/cron.d/typo3-scheduler
# Start cron daemon
service cron start
#!/bin/bash
# Remove stale Debian default index.html if it exists
# This ensures index.php is served instead
# The index.html can persist in Docker volumes across rebuilds
if [ -f /var/www/html/index.html ]; then
# Check if it's the Debian default page
if grep -q "Apache2 Debian Default Page" /var/www/html/index.html 2>/dev/null; then
rm -f /var/www/html/index.html
echo "Removed stale Debian default index.html"
fi
fi
# Checkpoints for typo3-ddev skill
# Validates DDEV configuration for TYPO3 projects
version: 1
skill_id: typo3-ddev
mechanical:
# === DDEV CONFIGURATION EXISTENCE ===
- id: DD-01
type: file_exists
target: .ddev/config.yaml
severity: error
desc: "DDEV config.yaml must exist"
- id: DD-02
type: file_exists
target: .ddev/.gitignore
severity: warning
desc: ".ddev/.gitignore should exist"
# === TYPO3 PROJECT TYPE ===
- id: DD-03
type: command
target: "grep -qE 'type:\\s*(typo3|php)' .ddev/config.yaml"
severity: warning
desc: "Project type should be typo3 or php. Use type: php for no_project_mount setups with custom docroots."
# === PHP VERSION CONFIGURATION ===
- id: DD-04
type: regex
target: .ddev/config.yaml
pattern: 'php_version:\s*"?[0-9]+\.[0-9]+"?'
severity: error
desc: "PHP version must be explicitly configured"
- id: DD-05
type: regex
target: .ddev/config.yaml
pattern: 'php_version:\s*"?8\.[1-9]"?'
severity: warning
desc: "PHP version should be 8.1 or higher for modern TYPO3"
# === DATABASE CONFIGURATION ===
# DDEV uses nested database config:
# database:
# type: mariadb
# version: "10.11"
- id: DD-06
type: command
pattern: "grep -A2 '^database:' .ddev/config.yaml 2>/dev/null | grep -E 'type:\\s*(mariadb|mysql)' || echo 'NOT_FOUND'"
severity: warning
desc: "Database type should be explicitly set (mariadb or mysql)"
- id: DD-07
type: command
pattern: "grep -A3 '^database:' .ddev/config.yaml 2>/dev/null | grep -E 'version:\\s*' || echo 'NOT_FOUND'"
severity: info
desc: "Database version should be explicitly configured (MariaDB 10.4+ or MySQL 8.0+)"
# === WEBSERVER CONFIGURATION ===
- id: DD-08
type: regex
target: .ddev/config.yaml
pattern: 'webserver_type:\s*(nginx-fpm|apache-fpm)'
severity: info
desc: "Webserver type should be explicitly configured"
# === DOCROOT CONFIGURATION ===
- id: DD-09
type: regex
target: .ddev/config.yaml
pattern: 'docroot:\s*(public|web|typo3|""|'')|no_project_mount:\s*true'
severity: warning
desc: "Docroot should be set to public, web, or typo3 for TYPO3 projects; empty docroot or no_project_mount: true is valid for custom multi-version extension setups with Apache vhosts"
# === ADDITIONAL SERVICES ===
- id: DD-10
type: not_contains
target: .ddev/config.yaml
pattern: 'no_mailpit: true'
severity: warning
desc: "Mailpit is enabled by default in DDEV v1.22+; if no_mailpit: true is set, email testing via Mailpit is disabled"
- id: DD-11
type: regex
target: .ddev/config.yaml
pattern: 'additional_hostnames:|additional_fqdns:'
severity: info
desc: "Additional hostnames may be configured for multisite setups"
# === GITIGNORE PATTERNS ===
- id: DD-12
type: contains
target: .ddev/.gitignore
pattern: "db_snapshots"
severity: warning
desc: ".ddev/.gitignore should exclude db_snapshots directory"
- id: DD-13
type: contains
target: .ddev/.gitignore
pattern: "import.yaml"
severity: info
desc: ".ddev/.gitignore should exclude import.yaml"
- id: DD-14
type: contains
target: .ddev/.gitignore
pattern: "import-db"
severity: info
desc: ".ddev/.gitignore should exclude import-db directory"
- id: DD-15
type: contains
target: .ddev/.gitignore
pattern: "sequelace.yaml"
severity: info
desc: ".ddev/.gitignore should exclude sequelace.yaml (Sequel Ace config)"
# === COMPOSER CONFIGURATION ===
- id: DD-16
type: regex
target: .ddev/config.yaml
pattern: 'composer_version:\s*"?2"?'
severity: warning
desc: "Composer version should be set to 2"
# === HOOKS CONFIGURATION ===
# `.ddev/commands/web/setup` is the Netresearch standard one-shot install
# entry point — runs inside the web container, performs `composer install`,
# database creation, docs render, asset preparation, etc. Single source of
# truth across all Netresearch TYPO3 projects. Makefile / shell aliases
# (e.g. `make up`) should delegate to `ddev setup`.
- id: DD-17
type: file_exists
target: .ddev/commands/web/setup
severity: warning
desc: ".ddev/commands/web/setup is the canonical one-shot install entry point (Netresearch convention — Makefile / aliases delegate to it)"
- id: DD-18
type: regex
target: .ddev/config.yaml
pattern: 'hooks:|post-start:'
severity: info
desc: "DDEV hooks may be configured for automated setup"
# === XDEBUG CONFIGURATION ===
- id: DD-19
type: regex
target: .ddev/config.yaml
pattern: 'xdebug_enabled:\s*(true|false)'
severity: info
desc: "Xdebug setting should be explicitly configured"
# === TIMEZONE CONFIGURATION ===
- id: DD-20
type: regex
target: .ddev/config.yaml
pattern: 'timezone:'
severity: info
desc: "Timezone may be explicitly configured"
# === CUSTOM DDEV COMMANDS ===
- id: DD-25
type: file_exists
target: .ddev/commands/
severity: info
desc: "Custom DDEV commands directory should exist for project automation"
- id: DD-26
type: file_exists
target: .ddev/commands/web/
severity: info
desc: ".ddev/commands/web/ should exist for custom web container commands"
# === TYPO3 CONTEXT ENVIRONMENT ===
- id: DD-27
type: regex
target: .ddev/config.yaml
pattern: 'TYPO3_CONTEXT|web_environment'
severity: info
desc: "DDEV should configure TYPO3_CONTEXT via web_environment"
# === NODEJS FOR FRONTEND TOOLING ===
- id: DD-28
type: regex
target: .ddev/config.yaml
pattern: 'nodejs_version:'
severity: info
desc: "DDEV nodejs_version may be configured for frontend build tools"
# === MULTI-VERSION TESTING SETUP ===
- id: DD-29
type: command
pattern: "ls .ddev/commands/web/install-* 2>/dev/null | head -1 || echo 'NOT_FOUND'"
severity: info
desc: "DDEV install commands should exist for multi-version TYPO3 testing"
llm_reviews:
# === PHP VERSION ALIGNMENT ===
- id: DD-21
domain: ddev-config
prompt: |
Verify PHP version consistency across the project:
1. Check .ddev/config.yaml php_version
2. Check composer.json require.php constraint
3. Check GitHub Actions CI matrix PHP versions
4. Ensure the DDEV PHP version is within the composer.json PHP constraint range
Report any version mismatches or inconsistencies.
severity: warning
desc: "PHP version should be consistent across DDEV, composer.json, and CI"
# === DATABASE VERSION ALIGNMENT ===
- id: DD-22
domain: ddev-config
prompt: |
Verify database configuration is appropriate for TYPO3:
1. Check if using MariaDB 10.4+ or MySQL 8.0+
2. For TYPO3 v12+, ensure MariaDB 10.4+ or MySQL 8.0+
3. For TYPO3 v13+, ensure MariaDB 10.5+ or MySQL 8.0+
Report if database version may not be compatible with TYPO3 version.
severity: info
desc: "Database version should be compatible with TYPO3 requirements"
# === ADDITIONAL SERVICES REVIEW ===
- id: DD-23
domain: ddev-config
prompt: |
Review additional DDEV services configuration:
1. Check if mailpit is configured for email testing (recommended)
2. Check for Redis/Memcached if caching extensions are used
3. Check for Elasticsearch/Solr if search extensions are used
4. Verify docker-compose.*.yaml files follow DDEV naming conventions
Report recommended services that may be missing based on project needs.
severity: info
desc: "Additional services should be configured based on project requirements"
# === TYPO3 SPECIFIC CONFIGURATION ===
- id: DD-24
domain: ddev-config
prompt: |
Review TYPO3-specific DDEV configuration:
1. Check if docroot points to the correct TYPO3 public directory
2. Verify web_environment settings for TYPO3 context
3. Check for TYPO3_CONTEXT environment variable configuration
4. Verify upload file size limits are adequate (upload_max_filesize, post_max_size)
Report any TYPO3-specific configuration improvements.
severity: info
desc: "DDEV should be properly configured for TYPO3 specifics"
Writing files / running PHP inside the container
Author the file with your editor and docker cp it in, then run a simple command. Don't fight shell quoting with nested heredocs or php -r — apostrophes and $ get mangled through ddev exec, and the container path may not be host-mounted, so the project mount isn't always reachable from where you wrote the file.
# {sitename} = name: from .ddev/config.yaml; {VERSION} = the TYPO3 version vhost.
# The web container is ddev-{sitename}-web. Note: $DDEV_SITENAME is only set
# INSIDE the container, so on the host shell name it explicitly.
docker cp snippet.php ddev-{sitename}-web:/tmp/snippet.php
ddev exec 'cat /tmp/snippet.php >> /var/www/html/v{VERSION}/config/system/additional.php' # append config
ddev exec 'php /tmp/snippet.php' # run a probePost-Setup Verification
After ddev start and installation, verify TYPO3 is fully functional:
# 1. Check DDEV status
ddev status # All services should be "running"
# 2. Verify backend is accessible
curl -sI https://v13.{sitename}.ddev.site/typo3/ | head -1
# Expected: HTTP/2 200 or HTTP/2 302 (redirect to login)
# 3. Verify extension is activated
ddev exec -d /var/www/html/v13 vendor/bin/typo3 extension:list --active | grep {extension_key}
# 4. Verify database is populated
ddev mysql -e "SHOW DATABASES;" | grep v13
ddev mysql v13 -e "SELECT COUNT(*) FROM be_users;"
# Expected: At least 1 backend userQuick health check (all-in-one):
ddev describe # Shows URLs, ports, database info
ddev exec -d /var/www/html/v13 vendor/bin/typo3 backend:lock:status
# Expected: "Backend is not locked"Generated Files
Copy templates from assets/templates/ to project's .ddev/:
.ddev/
├── config.yaml
├── docker-compose.web.yaml
├── apache/
│ ├── apache-site.conf (main site - gitignored by ddev!)
│ ├── v12.conf (committable - copy for TYPO3 v12)
│ ├── v13.conf (committable - copy for TYPO3 v13)
│ └── docs.conf (committable - for docs subdomain)
├── index.html.netresearch.template (for netresearch/* packages)
├── index.html.typo3.template (for all other packages)
└── commands/web/install-v{11,12,13,14}IMPORTANT: apache-site.conf is gitignored by ddev's default .gitignore. For multi-version setups, copy the separate v{11,12,13,14}.conf files which CAN be committed.