Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:43:24 +08:00
commit e1d41c1e9f
22 changed files with 7231 additions and 0 deletions

186
scripts/create-commit-message.py Executable file
View File

@@ -0,0 +1,186 @@
#!/usr/bin/env python3
"""
TYPO3 Core Contribution Commit Message Generator
Creates properly formatted commit messages following TYPO3 standards
"""
import argparse
import sys
import re
from typing import Optional
COMMIT_TYPES = {
'BUGFIX': 'Bug fixes',
'FEATURE': 'New features (main branch only)',
'TASK': 'Refactoring, cleanup, miscellaneous',
'DOCS': 'Documentation changes',
'SECURITY': 'Security vulnerability fixes'
}
BREAKING_CHANGE_PREFIX = '[!!!]'
def validate_subject(subject: str, has_breaking: bool) -> tuple[bool, Optional[str]]:
"""Validate subject line against TYPO3 rules"""
max_length = 52 if not has_breaking else 47 # Account for [!!!] prefix
if len(subject) > 72:
return False, "Subject line exceeds 72 characters (absolute limit)"
if len(subject) > max_length:
return False, f"Subject line exceeds {max_length} characters (recommended limit)"
if not subject[0].isupper():
return False, "Subject must start with uppercase letter"
if subject.endswith('.'):
return False, "Subject should not end with a period"
# Check for imperative mood (simple heuristic)
past_tense_endings = ['ed', 'ing']
first_word = subject.split()[0].lower()
if any(first_word.endswith(end) for end in past_tense_endings):
return False, f"Use imperative mood ('{first_word}' appears to be past/present continuous tense)"
return True, None
def wrap_text(text: str, width: int = 72) -> str:
"""Wrap text at specified width"""
words = text.split()
lines = []
current_line = []
current_length = 0
for word in words:
word_length = len(word)
if current_length + word_length + len(current_line) > width:
if current_line:
lines.append(' '.join(current_line))
current_line = [word]
current_length = word_length
else:
current_line.append(word)
current_length += word_length
if current_line:
lines.append(' '.join(current_line))
return '\n'.join(lines)
def parse_releases(releases_str: str) -> list[str]:
"""Parse comma-separated release versions"""
releases = [r.strip() for r in releases_str.split(',')]
# Validate format
valid_releases = []
for release in releases:
if release == 'main' or re.match(r'^\d+\.\d+$', release):
valid_releases.append(release)
else:
print(f"Warning: Invalid release format '{release}', skipping")
return valid_releases
def main():
parser = argparse.ArgumentParser(
description='Generate TYPO3-compliant commit messages',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''
Examples:
%(prog)s --issue 105737 --type BUGFIX
%(prog)s --issue 105737 --type FEATURE --breaking
%(prog)s --type TASK --related 12345,12346
'''
)
parser.add_argument('--issue', type=int, help='Forge issue number')
parser.add_argument('--related', help='Related issue numbers (comma-separated)')
parser.add_argument('--type', choices=COMMIT_TYPES.keys(), required=True,
help='Commit type')
parser.add_argument('--breaking', action='store_true',
help='Mark as breaking change (adds [!!!] prefix)')
parser.add_argument('--releases', default='main',
help='Target releases (comma-separated, e.g., "main, 13.4, 12.4")')
parser.add_argument('--output', help='Output file (default: print to stdout)')
args = parser.parse_args()
# Interactive mode
print("=== TYPO3 Commit Message Generator ===\n")
# Get subject line
print(f"Commit Type: [{args.type}]")
if args.breaking:
print(f"Breaking Change: Yes (will add {BREAKING_CHANGE_PREFIX} prefix)")
print()
subject = input("Enter subject line (max 52 chars, imperative mood): ").strip()
# Validate subject
valid, error = validate_subject(subject, args.breaking)
if not valid:
print(f"\n❌ Error: {error}")
sys.exit(1)
# Get description
print("\nEnter description (explain how and why, not what).")
print("Press Ctrl+D (Linux/Mac) or Ctrl+Z (Windows) when done:")
description_lines = []
try:
while True:
line = input()
description_lines.append(line)
except EOFError:
pass
description = '\n'.join(description_lines).strip()
if description:
description = wrap_text(description)
# Build commit message
type_prefix = f"{BREAKING_CHANGE_PREFIX}{args.type}" if args.breaking else args.type
message = f"[{type_prefix}] {subject}\n\n"
if description:
message += f"{description}\n\n"
# Add footer
if args.issue:
message += f"Resolves: #{args.issue}\n"
if args.related:
related_issues = [f"#{num.strip()}" for num in args.related.split(',')]
for issue in related_issues:
message += f"Related: {issue}\n"
releases = parse_releases(args.releases)
if releases:
message += f"Releases: {', '.join(releases)}\n"
# Output
print("\n" + "="*60)
print("Generated Commit Message:")
print("="*60)
print(message)
print("="*60)
print("\nNote: Change-Id will be added automatically by git hook")
print("="*60)
if args.output:
with open(args.output, 'w') as f:
f.write(message)
print(f"\n✓ Commit message saved to: {args.output}")
print(f" Use: git commit -F {args.output}")
else:
print("\nTo use this message:")
print(" 1. Copy the message above")
print(" 2. Run: git commit")
print(" 3. Paste into your editor")
return 0
if __name__ == '__main__':
sys.exit(main())

192
scripts/create-forge-issue.sh Executable file
View File

@@ -0,0 +1,192 @@
#!/bin/bash
# Create TYPO3 Forge issue via Redmine REST API
#
# Usage:
# 1. Get your API key from https://forge.typo3.org/my/account
# 2. Set environment variable: export FORGE_API_KEY="your-key-here"
# 3. Run: ./scripts/create-forge-issue.sh
set -e
# Color output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Check for API key
if [ -z "$FORGE_API_KEY" ]; then
echo -e "${RED}Error: FORGE_API_KEY environment variable not set${NC}"
echo ""
echo "Get your API key from: https://forge.typo3.org/my/account"
echo "Then set it: export FORGE_API_KEY=\"your-key-here\""
exit 1
fi
# Check for required tools
for tool in curl jq; do
if ! command -v $tool &> /dev/null; then
echo -e "${RED}Error: $tool is required but not installed${NC}"
exit 1
fi
done
echo -e "${GREEN}TYPO3 Forge Issue Creator${NC}"
echo ""
# Interactive prompts
read -p "Issue subject (title): " SUBJECT
if [ -z "$SUBJECT" ]; then
echo -e "${RED}Error: Subject is required${NC}"
exit 1
fi
echo ""
echo "Issue description (multi-line, press Ctrl+D when done):"
DESCRIPTION=$(cat)
if [ -z "$DESCRIPTION" ]; then
echo -e "${RED}Error: Description is required${NC}"
exit 1
fi
echo ""
echo "Select tracker type:"
echo " 1) Bug"
echo " 2) Feature"
echo " 3) Task"
read -p "Choice [1]: " TRACKER_CHOICE
TRACKER_CHOICE=${TRACKER_CHOICE:-1}
case $TRACKER_CHOICE in
1) TRACKER_ID=1; TRACKER_NAME="Bug" ;;
2) TRACKER_ID=2; TRACKER_NAME="Feature" ;;
3) TRACKER_ID=4; TRACKER_NAME="Task" ;;
*) echo -e "${RED}Invalid choice${NC}"; exit 1 ;;
esac
echo ""
echo "Select priority:"
echo " 1) Must have"
echo " 2) Should have (recommended)"
echo " 3) Could have"
read -p "Choice [2]: " PRIORITY_CHOICE
PRIORITY_CHOICE=${PRIORITY_CHOICE:-2}
case $PRIORITY_CHOICE in
1) PRIORITY_ID=3; PRIORITY_NAME="Must have" ;;
2) PRIORITY_ID=4; PRIORITY_NAME="Should have" ;;
3) PRIORITY_ID=5; PRIORITY_NAME="Could have" ;;
*) echo -e "${RED}Invalid choice${NC}"; exit 1 ;;
esac
echo ""
read -p "TYPO3 version affected (e.g., 13, 14) [13]: " TYPO3_VERSION
TYPO3_VERSION=${TYPO3_VERSION:-13}
echo ""
echo "Select category (common ones, or enter ID manually):"
echo " 1) Miscellaneous (975)"
echo " 2) Backend API (971)"
echo " 3) Backend User Interface (972)"
echo " 4) Frontend (977)"
echo " 5) Database API (974)"
echo " 6) Indexed Search (1000)"
echo " 7) Extension Manager (976)"
echo " 8) Documentation (1004)"
echo " 9) Enter category ID manually"
read -p "Choice [1]: " CATEGORY_CHOICE
CATEGORY_CHOICE=${CATEGORY_CHOICE:-1}
case $CATEGORY_CHOICE in
1) CATEGORY_ID=975; CATEGORY_NAME="Miscellaneous" ;;
2) CATEGORY_ID=971; CATEGORY_NAME="Backend API" ;;
3) CATEGORY_ID=972; CATEGORY_NAME="Backend User Interface" ;;
4) CATEGORY_ID=977; CATEGORY_NAME="Frontend" ;;
5) CATEGORY_ID=974; CATEGORY_NAME="Database API" ;;
6) CATEGORY_ID=1000; CATEGORY_NAME="Indexed Search" ;;
7) CATEGORY_ID=976; CATEGORY_NAME="Extension Manager" ;;
8) CATEGORY_ID=1004; CATEGORY_NAME="Documentation" ;;
9)
read -p "Enter category ID: " CATEGORY_ID
CATEGORY_NAME="Custom ($CATEGORY_ID)"
;;
*) echo -e "${RED}Invalid choice${NC}"; exit 1 ;;
esac
# Optional tags
echo ""
read -p "Tags (comma-separated, optional): " TAGS
# Summary
echo ""
echo -e "${YELLOW}Summary:${NC}"
echo " Tracker: $TRACKER_NAME"
echo " Subject: $SUBJECT"
echo " Priority: $PRIORITY_NAME"
echo " Category: $CATEGORY_NAME"
echo " TYPO3 Version: $TYPO3_VERSION"
[ -n "$TAGS" ] && echo " Tags: $TAGS"
echo ""
read -p "Create this issue? [Y/n]: " CONFIRM
CONFIRM=${CONFIRM:-Y}
if [[ ! $CONFIRM =~ ^[Yy]$ ]]; then
echo "Cancelled."
exit 0
fi
# Build JSON payload
JSON_PAYLOAD=$(jq -n \
--arg subject "$SUBJECT" \
--arg description "$DESCRIPTION" \
--argjson tracker "$TRACKER_ID" \
--argjson category "$CATEGORY_ID" \
--argjson priority "$PRIORITY_ID" \
--arg typo3_version "$TYPO3_VERSION" \
--arg tags "$TAGS" \
'{
issue: {
project_id: "typo3cms-core",
subject: $subject,
description: $description,
tracker_id: $tracker,
category_id: $category,
priority_id: $priority,
custom_fields: [
{id: 4, value: $typo3_version}
] + (if $tags != "" then [{id: 3, value: $tags}] else [] end)
}
}')
# Create issue
echo ""
echo -e "${YELLOW}Creating issue...${NC}"
RESPONSE=$(curl -s -X POST \
-H "Content-Type: application/json" \
-H "X-Redmine-API-Key: $FORGE_API_KEY" \
-d "$JSON_PAYLOAD" \
https://forge.typo3.org/issues.json)
# Check for errors
if echo "$RESPONSE" | jq -e '.errors' > /dev/null 2>&1; then
echo -e "${RED}Error creating issue:${NC}"
echo "$RESPONSE" | jq -r '.errors[]'
exit 1
fi
# Extract issue details
ISSUE_ID=$(echo "$RESPONSE" | jq -r '.issue.id')
ISSUE_URL="https://forge.typo3.org/issues/${ISSUE_ID}"
echo ""
echo -e "${GREEN}Success! Issue created:${NC}"
echo ""
echo " Issue #: $ISSUE_ID"
echo " URL: $ISSUE_URL"
echo ""
echo -e "${YELLOW}Next steps:${NC}"
echo " 1. Use in commit message: ${GREEN}Resolves: #${ISSUE_ID}${NC}"
echo " 2. Create feature branch: ${GREEN}git checkout -b feature/${ISSUE_ID}-description${NC}"
echo ""

102
scripts/query-forge-metadata.sh Executable file
View File

@@ -0,0 +1,102 @@
#!/bin/bash
# Query TYPO3 Forge project metadata via Redmine REST API
#
# Usage:
# 1. Get your API key from https://forge.typo3.org/my/account
# 2. Set environment variable: export FORGE_API_KEY="your-key-here"
# 3. Run: ./scripts/query-forge-metadata.sh [categories|trackers|all]
set -e
# Color output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Check for API key
if [ -z "$FORGE_API_KEY" ]; then
echo -e "${RED}Error: FORGE_API_KEY environment variable not set${NC}"
echo ""
echo "Get your API key from: https://forge.typo3.org/my/account"
echo "Then set it: export FORGE_API_KEY=\"your-key-here\""
exit 1
fi
# Check for required tools
for tool in curl jq; do
if ! command -v $tool &> /dev/null; then
echo -e "${RED}Error: $tool is required but not installed${NC}"
exit 1
fi
done
# Parse arguments
QUERY_TYPE=${1:-all}
# Fetch project metadata
echo -e "${YELLOW}Fetching TYPO3 Core project metadata...${NC}"
echo ""
RESPONSE=$(curl -s \
-H "X-Redmine-API-Key: $FORGE_API_KEY" \
https://forge.typo3.org/projects/typo3cms-core.json)
# Check for errors
if echo "$RESPONSE" | jq -e '.errors' > /dev/null 2>&1; then
echo -e "${RED}Error querying Forge:${NC}"
echo "$RESPONSE" | jq -r '.errors[]'
exit 1
fi
# Display trackers
if [[ "$QUERY_TYPE" == "trackers" || "$QUERY_TYPE" == "all" ]]; then
echo -e "${GREEN}=== Trackers ===${NC}"
echo ""
echo "$RESPONSE" | jq -r '.project.trackers[] | "\(.id)\t\(.name)"' | \
awk -F'\t' '{printf " %-4s %s\n", $1, $2}'
echo ""
fi
# Display categories
if [[ "$QUERY_TYPE" == "categories" || "$QUERY_TYPE" == "all" ]]; then
echo -e "${GREEN}=== Issue Categories ===${NC}"
echo ""
echo "$RESPONSE" | jq -r '.project.issue_categories[] | "\(.id)\t\(.name)"' | \
awk -F'\t' '{printf " %-6s %s\n", $1, $2}'
echo ""
fi
# Display usage examples
if [[ "$QUERY_TYPE" == "all" ]]; then
echo -e "${BLUE}=== Usage Examples ===${NC}"
echo ""
echo "Create bug in Backend API category:"
echo ' curl -X POST \'
echo ' -H "Content-Type: application/json" \'
echo ' -H "X-Redmine-API-Key: $FORGE_API_KEY" \'
echo ' -d '"'"'{'
echo ' "issue": {'
echo ' "project_id": "typo3cms-core",'
echo ' "subject": "Issue title",'
echo ' "description": "Description",'
echo ' "tracker_id": 1,'
echo ' "category_id": 971,'
echo ' "priority_id": 4,'
echo ' "custom_fields": [{"id": 4, "value": "13"}]'
echo ' }'
echo ' }'"'"' \'
echo ' https://forge.typo3.org/issues.json'
echo ""
echo "Or use the interactive script:"
echo " ./scripts/create-forge-issue.sh"
echo ""
fi
# Save to file if requested
if [[ "$2" == "--save" ]]; then
OUTPUT_FILE="forge-metadata-$(date +%Y%m%d).json"
echo "$RESPONSE" > "$OUTPUT_FILE"
echo -e "${GREEN}Metadata saved to: $OUTPUT_FILE${NC}"
fi

433
scripts/setup-typo3-coredev.sh Executable file
View File

@@ -0,0 +1,433 @@
#!/bin/bash
# TYPO3 Core Development Environment Setup Script
# Based on proven production workflow
# Creates complete DDEV-based TYPO3 Core development environment
set -e
# Colors
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color
# Print functions
print_header() {
echo -e "\n${BLUE}===================================================${NC}"
echo -e "${BLUE}$1${NC}"
echo -e "${BLUE}===================================================${NC}\n"
}
print_step() {
echo -e "${GREEN}${NC} $1"
}
print_info() {
echo -e "${YELLOW}${NC} $1"
}
print_error() {
echo -e "${RED}${NC} $1"
}
print_success() {
echo -e "${GREEN}${NC} $1"
}
# Check prerequisites
check_prerequisites() {
print_header "Checking Prerequisites"
local missing_prereqs=false
# Check Git
if command -v git &> /dev/null; then
print_success "Git: $(git --version)"
else
print_error "Git not found. Please install Git first."
missing_prereqs=true
fi
# Check DDEV
if command -v ddev &> /dev/null; then
print_success "DDEV: $(ddev version | head -n1)"
else
print_error "DDEV not found. Please install DDEV first: https://ddev.readthedocs.io/"
missing_prereqs=true
fi
# Check Docker
if command -v docker &> /dev/null; then
if docker ps &> /dev/null; then
print_success "Docker: Running"
else
print_error "Docker not running. Please start Docker."
missing_prereqs=true
fi
else
print_error "Docker not found. DDEV requires Docker."
missing_prereqs=true
fi
if [ "$missing_prereqs" = true ]; then
print_error "Missing prerequisites. Please install required tools and try again."
exit 1
fi
}
# Gather user input
gather_input() {
print_header "Configuration"
# Project name
read -p "Project name (e.g., t3coredev-14-php8-4): " PROJECT_NAME
if [ -z "$PROJECT_NAME" ]; then
PROJECT_NAME="t3coredev-14-php8-4"
print_info "Using default: $PROJECT_NAME"
fi
# Git user name
read -p "Your name for Git commits: " GIT_NAME
while [ -z "$GIT_NAME" ]; do
print_error "Name is required"
read -p "Your name for Git commits: " GIT_NAME
done
# Git email
read -p "Your email for Git commits: " GIT_EMAIL
while [ -z "$GIT_EMAIL" ]; do
print_error "Email is required"
read -p "Your email for Git commits: " GIT_EMAIL
done
# Gerrit username
read -p "Your Gerrit username (review.typo3.org): " GERRIT_USER
while [ -z "$GERRIT_USER" ]; do
print_error "Gerrit username is required"
read -p "Your Gerrit username: " GERRIT_USER
done
# PHP version
read -p "PHP version (8.2, 8.3, 8.4) [default: 8.4]: " PHP_VERSION
if [ -z "$PHP_VERSION" ]; then
PHP_VERSION="8.4"
fi
# Timezone
read -p "Timezone [default: Europe/Vienna]: " TIMEZONE
if [ -z "$TIMEZONE" ]; then
TIMEZONE="Europe/Vienna"
fi
# Admin password
read -sp "TYPO3 admin password: " ADMIN_PASSWORD
echo
while [ -z "$ADMIN_PASSWORD" ]; do
print_error "Admin password is required"
read -sp "TYPO3 admin password: " ADMIN_PASSWORD
echo
done
# Confirm
echo -e "\n${YELLOW}Configuration Summary:${NC}"
echo " Project: $PROJECT_NAME"
echo " Git Name: $GIT_NAME"
echo " Git Email: $GIT_EMAIL"
echo " Gerrit User: $GERRIT_USER"
echo " PHP Version: $PHP_VERSION"
echo " Timezone: $TIMEZONE"
echo
read -p "Proceed with setup? (y/n): " CONFIRM
if [[ ! "$CONFIRM" =~ ^[Yy]$ ]]; then
print_info "Setup cancelled."
exit 0
fi
}
# Create project directory
create_project_dir() {
print_header "Creating Project Directory"
if [ -d "$PROJECT_NAME" ]; then
print_error "Directory $PROJECT_NAME already exists!"
read -p "Delete and recreate? (y/n): " DELETE_CONFIRM
if [[ "$DELETE_CONFIRM" =~ ^[Yy]$ ]]; then
rm -rf "$PROJECT_NAME"
print_success "Deleted existing directory"
else
print_error "Cannot proceed with existing directory"
exit 1
fi
fi
mkdir -p "$PROJECT_NAME"
cd "$PROJECT_NAME"
print_success "Created and entered directory: $PROJECT_NAME"
}
# Clone TYPO3 repository
clone_repository() {
print_header "Cloning TYPO3 Repository"
print_step "Cloning from GitHub..."
if git clone git@github.com:typo3/typo3 . 2>&1 | grep -q "Permission denied\|Could not"; then
print_error "Failed to clone via SSH. Trying HTTPS..."
rm -rf .git
if ! git clone https://github.com/typo3/typo3.git . ; then
print_error "Failed to clone repository"
exit 1
fi
fi
print_success "Repository cloned successfully"
}
# Configure Git
configure_git() {
print_header "Configuring Git"
print_step "Setting user identity..."
git config user.name "$GIT_NAME"
git config user.email "$GIT_EMAIL"
print_success "User identity configured"
print_step "Enabling automatic rebase..."
git config branch.autosetuprebase remote
print_success "Automatic rebase enabled"
print_step "Installing git hooks..."
if [ -f "Build/git-hooks/commit-msg" ]; then
cp Build/git-hooks/commit-msg .git/hooks/commit-msg
chmod +x .git/hooks/commit-msg
print_success "Commit-msg hook installed"
else
print_error "Commit-msg hook not found in Build/git-hooks/"
fi
print_step "Configuring Gerrit remote..."
git config remote.origin.pushurl "ssh://${GERRIT_USER}@review.typo3.org:29418/Packages/TYPO3.CMS.git"
git config remote.origin.push "+refs/heads/main:refs/for/main"
print_success "Gerrit remote configured"
# Test Gerrit connection
print_step "Testing Gerrit SSH connection..."
if timeout 5 ssh -p 29418 -o StrictHostKeyChecking=no -o BatchMode=yes "${GERRIT_USER}@review.typo3.org" gerrit version &>/dev/null; then
print_success "Gerrit connection successful"
else
print_error "Cannot connect to Gerrit. Please verify your SSH keys are configured."
print_info "Continue anyway? SSH key might need configuration."
read -p "Continue? (y/n): " CONTINUE
if [[ ! "$CONTINUE" =~ ^[Yy]$ ]]; then
exit 1
fi
fi
}
# Configure DDEV
configure_ddev() {
print_header "Configuring DDEV"
print_step "Setting project type..."
ddev config --project-type typo3 -y
print_step "Configuring timezone..."
ddev config --timezone "$TIMEZONE"
print_step "Setting PHP version..."
ddev config --php-version="$PHP_VERSION"
print_step "Configuring webserver..."
ddev config --webserver-type=apache-fpm
print_step "Setting database version..."
ddev config --database=mariadb:10.6
print_step "Adding environment variables..."
ddev config --web-environment-add="TYPO3_CONTEXT=Development/Ddev"
ddev config --web-environment-add="COMPOSER_ROOT_VERSION=14.0.x-dev"
print_success "DDEV configured successfully"
}
# Start DDEV
start_ddev() {
print_header "Starting DDEV"
print_step "Starting containers..."
if ddev start; then
print_success "DDEV started successfully"
ddev describe
else
print_error "Failed to start DDEV"
exit 1
fi
}
# Install dependencies
install_dependencies() {
print_header "Installing Dependencies"
print_step "Running Composer install via runTests.sh..."
if ./Build/Scripts/runTests.sh -s composerInstall; then
print_success "Dependencies installed"
else
print_error "Failed to install dependencies"
print_info "Trying alternative method..."
if ddev composer install; then
print_success "Dependencies installed via ddev composer"
else
print_error "Failed to install dependencies"
exit 1
fi
fi
}
# Setup TYPO3
setup_typo3() {
print_header "Setting Up TYPO3"
print_step "Creating installation trigger files..."
ddev exec 'touch /var/www/html/FIRST_INSTALL'
ddev exec 'touch /var/www/html/typo3conf/ENABLE_INSTALL_TOOL'
ddev exec 'echo "KEEP_FILE" > /var/www/html/typo3conf/ENABLE_INSTALL_TOOL'
print_success "Trigger files created"
print_step "Running TYPO3 setup..."
if ddev typo3 setup \
--driver=mysqli \
--host=db \
--port=3306 \
--dbname=db \
--username=db \
--password=db \
--admin-username=backenduser \
--admin-user-password="$ADMIN_PASSWORD" \
--admin-email="$GIT_EMAIL" \
--project-name="TYPO3 Core Dev v14 PHP ${PHP_VERSION}" \
--no-interaction \
--server-type=apache \
--force; then
print_success "TYPO3 setup completed"
else
print_error "TYPO3 setup failed"
exit 1
fi
}
# Activate extensions
activate_extensions() {
print_header "Activating Extensions"
print_step "Setting up extensions..."
ddev typo3 extension:setup
print_step "Activating indexed_search..."
ddev typo3 extension:activate indexed_search
print_step "Activating styleguide..."
ddev typo3 extension:activate styleguide
print_step "Activating scheduler..."
ddev typo3 extension:activate scheduler
print_success "Extensions activated"
}
# Setup backend groups
setup_backend_groups() {
print_header "Setting Up Backend User Groups"
print_step "Creating default backend groups..."
if ddev typo3 setup:begroups:default --groups=Both; then
print_success "Backend groups configured"
else
print_error "Failed to setup backend groups"
fi
}
# Generate test data
generate_test_data() {
print_header "Generating Test Data"
read -p "Generate styleguide test data? (y/n): " GENERATE_DATA
if [[ "$GENERATE_DATA" =~ ^[Yy]$ ]]; then
print_step "Generating TCA examples..."
ddev typo3 styleguide:generate --create -- tca
print_step "Generating frontend system template..."
ddev typo3 styleguide:generate --create -- frontend-systemplate
print_success "Test data generated"
else
print_info "Skipping test data generation"
fi
}
# Final steps
finalize() {
print_header "Setup Complete!"
print_success "TYPO3 Core development environment is ready!"
echo
echo -e "${GREEN}Project Details:${NC}"
echo " Name: $PROJECT_NAME"
echo " URL: https://${PROJECT_NAME}.ddev.site"
echo " Backend: https://${PROJECT_NAME}.ddev.site/typo3"
echo " Admin User: backenduser"
echo " Admin Password: [the password you entered]"
echo
echo -e "${GREEN}Next Steps:${NC}"
echo " 1. Open backend: ddev launch /typo3"
echo " 2. Run tests: ./Build/Scripts/runTests.sh -s unit"
echo " 3. Create branch: git checkout -b feature/your-feature"
echo " 4. Make changes and commit with proper message"
echo " 5. Push to Gerrit: git push origin HEAD:refs/for/main"
echo
echo -e "${GREEN}Useful Commands:${NC}"
echo " ddev start - Start project"
echo " ddev stop - Stop project"
echo " ddev restart - Restart project"
echo " ddev ssh - SSH into container"
echo " ddev typo3 cache:flush - Clear TYPO3 caches"
echo " ddev logs -f - Follow logs"
echo
read -p "Open TYPO3 backend now? (y/n): " OPEN_BACKEND
if [[ "$OPEN_BACKEND" =~ ^[Yy]$ ]]; then
ddev launch /typo3
fi
}
# Main execution
main() {
clear
print_header "TYPO3 Core Development Setup"
echo "This script will set up a complete TYPO3 Core development environment."
echo "It will:"
echo " - Clone TYPO3 Core repository"
echo " - Configure Git for Gerrit submissions"
echo " - Set up DDEV with optimal settings"
echo " - Install TYPO3 with test data"
echo " - Activate development extensions"
echo
check_prerequisites
gather_input
create_project_dir
clone_repository
configure_git
configure_ddev
start_ddev
install_dependencies
setup_typo3
activate_extensions
setup_backend_groups
generate_test_data
finalize
}
# Run main function
main

View File

@@ -0,0 +1,256 @@
#!/usr/bin/env python3
"""
TYPO3 Commit Message Validator
Validates commit messages against TYPO3 contribution standards
"""
import sys
import re
import argparse
from typing import List, Tuple
VALID_TYPES = ['BUGFIX', 'FEATURE', 'TASK', 'DOCS', 'SECURITY']
BREAKING_PREFIX = '[!!!]'
class CommitMessageValidator:
def __init__(self, message: str):
self.message = message
self.lines = message.split('\n')
self.errors = []
self.warnings = []
def validate(self) -> Tuple[bool, List[str], List[str]]:
"""Run all validation checks"""
self.check_subject_line()
self.check_blank_line()
self.check_footer()
self.check_change_id()
return len(self.errors) == 0, self.errors, self.warnings
def check_subject_line(self):
"""Validate the subject line"""
if not self.lines:
self.errors.append("Commit message is empty")
return
subject = self.lines[0]
# Check for commit type
type_pattern = r'^\[(?:\[!!!\])?(BUGFIX|FEATURE|TASK|DOCS|SECURITY)\]'
match = re.match(type_pattern, subject)
if not match:
self.errors.append(
f"Subject must start with commit type: {', '.join(f'[{t}]' for t in VALID_TYPES)}"
)
return
commit_type = match.group(1)
# Check for breaking change prefix
if subject.startswith('[!!!]'):
if commit_type == 'BUGFIX':
self.warnings.append(
"Breaking changes are unusual for BUGFIX. Consider using FEATURE or TASK"
)
# Extract subject without type prefix
subject_without_type = re.sub(type_pattern, '', subject).strip()
# Check length
if len(subject) > 72:
self.errors.append(
f"Subject line is {len(subject)} characters (max 72). Current: {len(subject)}"
)
elif len(subject) > 52:
self.warnings.append(
f"Subject line is {len(subject)} characters (recommended max 52)"
)
# Check capitalization
if subject_without_type and not subject_without_type[0].isupper():
self.errors.append("Subject description must start with uppercase letter")
# Check for period at end
if subject.endswith('.'):
self.errors.append("Subject line should not end with a period")
# Check for imperative mood (heuristic)
if subject_without_type:
first_word = subject_without_type.split()[0].lower()
if first_word.endswith('ed') or first_word.endswith('ing'):
self.warnings.append(
f"Use imperative mood: '{first_word}' may not be imperative. "
"Use 'Fix' not 'Fixed' or 'Fixing'"
)
def check_blank_line(self):
"""Check for blank line after subject"""
if len(self.lines) < 2:
return # Only subject line, no body
if len(self.lines) >= 2 and self.lines[1] != '':
self.errors.append("Second line must be blank (separate subject from body)")
def check_footer(self):
"""Check footer tags"""
footer_pattern = r'^(Resolves|Related|Releases|Depends|Reverts):\s*'
has_resolves = False
has_releases = False
has_change_id = False
for i, line in enumerate(self.lines):
if re.match(footer_pattern, line):
# Check format: should have colon followed by space
if not re.match(r'^[A-Z][a-z]+:\s+', line):
self.errors.append(
f"Line {i+1}: Footer tag must have colon followed by space: '{line}'"
)
# Check specific tags
if line.startswith('Resolves:'):
has_resolves = True
# Validate issue number format
if not re.match(r'^Resolves:\s+#\d+', line):
self.errors.append(
f"Line {i+1}: Resolves must reference issue number: 'Resolves: #12345'"
)
elif line.startswith('Related:'):
if not re.match(r'^Related:\s+#\d+', line):
self.errors.append(
f"Line {i+1}: Related must reference issue number: 'Related: #12345'"
)
elif line.startswith('Releases:'):
has_releases = True
# Validate releases format
releases_value = line.split(':', 1)[1].strip()
releases = [r.strip() for r in releases_value.split(',')]
for release in releases:
if release != 'main' and not re.match(r'^\d+\.\d+$', release):
self.errors.append(
f"Line {i+1}: Invalid release format '{release}'. "
"Use 'main' or version like '13.4'"
)
elif line.startswith('Change-Id:'):
has_change_id = True
# Warnings for missing tags
if not has_resolves:
self.warnings.append(
"No 'Resolves: #<issue>' tag found. Required for features and tasks."
)
if not has_releases:
self.warnings.append(
"No 'Releases:' tag found. Required to specify target versions."
)
def check_change_id(self):
"""Check for Change-Id"""
change_id_pattern = r'^Change-Id:\s+I[a-f0-9]{40}$'
has_change_id = any(re.match(change_id_pattern, line) for line in self.lines)
if not has_change_id:
self.warnings.append(
"No Change-Id found. It will be added automatically by git commit-msg hook."
)
def check_line_length(self):
"""Check body line lengths"""
for i, line in enumerate(self.lines[2:], start=3): # Skip subject and blank line
if line.startswith(('Resolves:', 'Related:', 'Releases:', 'Change-Id:', 'Depends:', 'Reverts:')):
continue # Skip footer
if len(line) > 72:
# Allow URLs to be longer
if not re.search(r'https?://', line):
self.warnings.append(
f"Line {i}: Length {len(line)} exceeds 72 characters"
)
def main():
parser = argparse.ArgumentParser(
description='Validate TYPO3 commit messages',
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument('--file', '-f', help='File containing commit message')
parser.add_argument('--message', '-m', help='Commit message string')
parser.add_argument('--strict', action='store_true',
help='Treat warnings as errors')
args = parser.parse_args()
# Get message
if args.file:
try:
with open(args.file, 'r') as f:
message = f.read()
except FileNotFoundError:
print(f"Error: File not found: {args.file}")
return 1
elif args.message:
message = args.message
else:
# Read from last commit
try:
import subprocess
result = subprocess.run(
['git', 'log', '-1', '--pretty=%B'],
capture_output=True,
text=True,
check=True
)
message = result.stdout
except subprocess.CalledProcessError:
print("Error: Could not read last commit message")
print("Usage: Provide --file or --message, or run in a git repository")
return 1
# Validate
validator = CommitMessageValidator(message)
is_valid, errors, warnings = validator.validate()
# Print results
print("=" * 60)
print("TYPO3 Commit Message Validation")
print("=" * 60)
print()
if errors:
print("❌ ERRORS:")
for error in errors:
print(f"{error}")
print()
if warnings:
print("⚠️ WARNINGS:")
for warning in warnings:
print(f"{warning}")
print()
if not errors and not warnings:
print("✅ Commit message is valid!")
elif not errors:
print("✅ No errors found (warnings can be ignored)")
else:
print("❌ Validation failed. Please fix errors above.")
print("=" * 60)
# Exit code
if errors or (args.strict and warnings):
return 1
return 0
if __name__ == '__main__':
sys.exit(main())

181
scripts/verify-prerequisites.sh Executable file
View File

@@ -0,0 +1,181 @@
#!/bin/bash
# TYPO3 Core Contribution Prerequisites Checker
# Verifies accounts, git configuration, and development environment setup
set -e
# Colors for output
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo "================================================"
echo "TYPO3 Core Contribution Prerequisites Check"
echo "================================================"
echo
# Track overall status
ALL_CHECKS_PASSED=true
# Function to print status
print_status() {
if [ "$1" = "pass" ]; then
echo -e "${GREEN}${NC} $2"
elif [ "$1" = "fail" ]; then
echo -e "${RED}${NC} $2"
ALL_CHECKS_PASSED=false
elif [ "$1" = "warn" ]; then
echo -e "${YELLOW}${NC} $2"
fi
}
# 1. Check Git installation
echo "1. Checking Git installation..."
if command -v git &> /dev/null; then
GIT_VERSION=$(git --version)
print_status "pass" "Git installed: $GIT_VERSION"
else
print_status "fail" "Git not installed"
fi
echo
# 2. Check Git user configuration
echo "2. Checking Git user configuration..."
GIT_USER_NAME=$(git config --global user.name 2>/dev/null || echo "")
GIT_USER_EMAIL=$(git config --global user.email 2>/dev/null || echo "")
if [ -n "$GIT_USER_NAME" ] && [ -n "$GIT_USER_EMAIL" ]; then
print_status "pass" "Git user configured: $GIT_USER_NAME <$GIT_USER_EMAIL>"
else
print_status "fail" "Git user not configured. Run:"
echo " git config --global user.name \"Your Name\""
echo " git config --global user.email \"your-email@example.org\""
fi
echo
# 2a. Verify Git email matches Gerrit account
echo "2a. Verifying Git email against Gerrit..."
if [ -n "$GIT_USER_EMAIL" ]; then
echo " Your Git email: $GIT_USER_EMAIL"
print_status "warn" " IMPORTANT: Verify this email is registered at:"
echo " https://review.typo3.org/settings#EmailAddresses"
echo " Gerrit will reject pushes if email doesn't match!"
fi
echo
# 3. Check if in TYPO3 repository
echo "3. Checking TYPO3 repository..."
if [ -d ".git" ]; then
REPO_URL=$(git config --get remote.origin.url 2>/dev/null || echo "")
if [[ "$REPO_URL" == *"typo3"* ]]; then
print_status "pass" "In TYPO3 repository"
# Check TYPO3-specific git config
echo " Checking TYPO3-specific configuration..."
# Check auto-rebase
AUTO_REBASE=$(git config --get branch.autosetuprebase 2>/dev/null || echo "")
if [ "$AUTO_REBASE" = "remote" ]; then
print_status "pass" " Auto-rebase configured"
else
print_status "fail" " Auto-rebase not configured. Run: git config branch.autosetuprebase remote"
fi
# Check Gerrit push URL
PUSH_URL=$(git config --get remote.origin.pushurl 2>/dev/null || echo "")
if [[ "$PUSH_URL" == *"review.typo3.org"* ]]; then
print_status "pass" " Gerrit push URL configured"
else
print_status "fail" " Gerrit push URL not configured. Run:"
echo " git config remote.origin.pushurl ssh://<USERNAME>@review.typo3.org:29418/Packages/TYPO3.CMS.git"
fi
# Check push refspec
PUSH_REFSPEC=$(git config --get remote.origin.push 2>/dev/null || echo "")
if [[ "$PUSH_REFSPEC" == *"refs/for/main"* ]]; then
print_status "pass" " Push refspec configured for Gerrit"
else
print_status "fail" " Push refspec not configured. Run:"
echo " git config remote.origin.push +refs/heads/main:refs/for/main"
fi
else
print_status "warn" "In git repository but not TYPO3. URL: $REPO_URL"
fi
else
print_status "warn" "Not in a git repository (run from TYPO3 repo root)"
fi
echo
# 4. Check Git hooks
echo "4. Checking Git hooks..."
if [ -f ".git/hooks/commit-msg" ]; then
print_status "pass" "commit-msg hook installed"
else
print_status "fail" "commit-msg hook not installed. Run: composer gerrit:setup"
fi
if [ -f ".git/hooks/pre-commit" ]; then
print_status "pass" "pre-commit hook installed"
else
print_status "warn" "pre-commit hook not installed (optional but recommended)"
fi
echo
# 5. Check SSH connection to Gerrit
echo "5. Checking Gerrit SSH connection..."
if timeout 5 ssh -p 29418 -o StrictHostKeyChecking=no -o BatchMode=yes review.typo3.org gerrit version &>/dev/null; then
print_status "pass" "Gerrit SSH connection successful"
else
print_status "fail" "Cannot connect to Gerrit via SSH. Check your SSH keys and Gerrit setup"
fi
echo
# 6. Check Composer
echo "6. Checking Composer installation..."
if command -v composer &> /dev/null; then
COMPOSER_VERSION=$(composer --version 2>/dev/null | head -n1)
print_status "pass" "Composer installed: $COMPOSER_VERSION"
else
print_status "warn" "Composer not found (needed for running tests and gerrit:setup)"
fi
echo
# 7. Check PHP
echo "7. Checking PHP installation..."
if command -v php &> /dev/null; then
PHP_VERSION=$(php -v | head -n1)
print_status "pass" "PHP installed: $PHP_VERSION"
else
print_status "warn" "PHP not found (needed for development and testing)"
fi
echo
# 8. Check DDEV (optional)
echo "8. Checking DDEV installation (optional)..."
if command -v ddev &> /dev/null; then
DDEV_VERSION=$(ddev version | head -n1)
print_status "pass" "DDEV installed: $DDEV_VERSION"
else
print_status "warn" "DDEV not found (recommended for development environment)"
fi
echo
# Final Summary
echo "================================================"
if [ "$ALL_CHECKS_PASSED" = true ]; then
echo -e "${GREEN}✓ All critical checks passed!${NC}"
echo "You're ready to contribute to TYPO3 Core."
else
echo -e "${RED}✗ Some checks failed.${NC}"
echo "Please address the issues above before contributing."
fi
echo "================================================"
# Exit with appropriate code
if [ "$ALL_CHECKS_PASSED" = true ]; then
exit 0
else
exit 1
fi