diff --git a/.config/php/error-reporting.ini b/.config/php/error-reporting.ini deleted file mode 100644 index 0d86f39..0000000 --- a/.config/php/error-reporting.ini +++ /dev/null @@ -1,158 +0,0 @@ -; ============================================================================ -; KaririCode DevKit - Error Reporting Configuration -; ============================================================================ -; Strict error reporting configuration for development environment -; All errors, warnings, and notices are displayed to catch issues early -; -; Location: devkit/.config/php/error-reporting.ini -; ============================================================================ - -[PHP] -; ============================================================================ -; ERROR REPORTING -; ============================================================================ -; Report all errors, warnings, and notices -error_reporting = E_ALL - -; Display errors on screen (development only) -display_errors = On -display_startup_errors = On - -; Log errors to file -log_errors = On -error_log = /var/log/php_errors.log - -; Detailed error messages -html_errors = On -docref_root = "https://www.php.net/manual/en/" -docref_ext = .html - -; Track all errors -track_errors = Off -xmlrpc_errors = Off - -; ============================================================================ -; ASSERTIONS -; ============================================================================ -; Enable assertions for development -zend.assertions = 1 -assert.active = 1 -assert.exception = 1 -assert.warning = 0 -assert.bail = 0 - -; ============================================================================ -; DEVELOPMENT SETTINGS -; ============================================================================ -; Hide PHP version in headers (security) -expose_php = Off - -; Variables order -variables_order = "EGPCS" -request_order = "GP" - -; Auto-detect line endings -auto_detect_line_endings = Off - -; ============================================================================ -; RESOURCE LIMITS -; ============================================================================ -; Memory limit (generous for development) -memory_limit = 512M - -; Maximum execution time -max_execution_time = 30 -max_input_time = 60 - -; Input size limits -post_max_size = 25M -upload_max_filesize = 20M -max_file_uploads = 20 - -; ============================================================================ -; OUTPUT BUFFERING -; ============================================================================ -; Output buffering (off for immediate error display) -output_buffering = Off -implicit_flush = On - -; ============================================================================ -; DATE/TIME -; ============================================================================ -; Default timezone -date.timezone = UTC - -; ============================================================================ -; SESSION -; ============================================================================ -; Session configuration -session.save_handler = files -session.save_path = "/tmp" -session.use_strict_mode = 1 -session.use_cookies = 1 -session.use_only_cookies = 1 -session.cookie_httponly = 1 -session.cookie_secure = 0 -session.cookie_samesite = "Lax" -session.gc_probability = 1 -session.gc_divisor = 100 -session.gc_maxlifetime = 1440 -session.sid_length = 48 -session.sid_bits_per_character = 6 - -; ============================================================================ -; REALPATH CACHE -; ============================================================================ -; Realpath cache (keep small for development) -realpath_cache_size = 4096K -realpath_cache_ttl = 120 - -; ============================================================================ -; FILE UPLOADS -; ============================================================================ -; File uploads enabled -file_uploads = On -upload_tmp_dir = /tmp - -; ============================================================================ -; SECURITY -; ============================================================================ -; Disable dangerous functions (customize as needed) -disable_functions = -disable_classes = - -; ============================================================================ -; MAIL -; ============================================================================ -; Mail configuration (usually handled by application) -SMTP = localhost -smtp_port = 25 -sendmail_path = /usr/sbin/sendmail -t -i - -; ============================================================================ -; MISC -; ============================================================================ -; Allow URL fopen (needed for many libraries) -allow_url_fopen = On -allow_url_include = Off - -; Auto-prepend/append files -auto_prepend_file = -auto_append_file = - -; Default charset -default_charset = "UTF-8" - -; Maximum input variables (prevent resource exhaustion) -max_input_vars = 1000 -max_input_nesting_level = 64 - -; ============================================================================ -; EXTENSIONS -; ============================================================================ -; Common extensions are enabled by default in kariricode/php-api-stack -; Additional extensions can be enabled as needed: -; extension=redis.so -; extension=memcached.so -; extension=apcu.so -; extension=imagick.so \ No newline at end of file diff --git a/.config/php/xdebug.ini b/.config/php/xdebug.ini deleted file mode 100644 index 462643e..0000000 --- a/.config/php/xdebug.ini +++ /dev/null @@ -1,160 +0,0 @@ -; ============================================================================ -; KaririCode DevKit - Xdebug Configuration -; ============================================================================ -; Xdebug 3.x configuration for step debugging and code coverage -; https://xdebug.org/docs/all_settings -; -; Location: devkit/.config/php/xdebug.ini -; ============================================================================ - -[xdebug] -; ============================================================================ -; MODE CONFIGURATION -; ============================================================================ -; Modes: off, develop, coverage, debug, gcstats, profile, trace -; Multiple modes can be combined with commas (e.g., "debug,coverage") -; This is controlled by environment variable XDEBUG_MODE in .env -xdebug.mode=${XDEBUG_MODE} - -; ============================================================================ -; DEBUGGING -; ============================================================================ -; Start debugging automatically or wait for trigger -; Values: yes, no, trigger -xdebug.start_with_request=yes - -; IDE/Client connection settings -; Use host.docker.internal for Docker Desktop (Mac/Windows) -; Use 172.17.0.1 for Docker on Linux -xdebug.client_host=host.docker.internal -xdebug.client_port=9003 - -; Discovery mode for cloud/dynamic environments -; Set to 1 if you need automatic discovery (not recommended for local dev) -xdebug.discover_client_host=0 - -; IDE key for identifying debugging session -; PHPStorm: PHPSTORM -; VSCode: VSCODE -xdebug.idekey=PHPSTORM - -; Connection timeout in milliseconds -xdebug.connect_timeout_ms=2000 - -; ============================================================================ -; LOGGING -; ============================================================================ -; Log file location (useful for debugging connection issues) -xdebug.log=/var/log/xdebug.log - -; Log level (0-10, where 10 is most verbose) -; 0 = Criticals -; 1 = Errors -; 3 = Warnings -; 5 = Communication -; 7 = Information -; 10 = Debug -xdebug.log_level=7 - -; ============================================================================ -; STEP DEBUGGING -; ============================================================================ -; Maximum nesting level for recursive debugging -; Increase if you have deeply nested structures -xdebug.max_nesting_level=512 - -; ============================================================================ -; COVERAGE -; ============================================================================ -; Enable code coverage (required for PHPUnit coverage) -xdebug.coverage_enable=1 - -; ============================================================================ -; DEVELOPMENT MODE -; ============================================================================ -; Development helpers (when mode=develop) -; Show local variables in stack traces -xdebug.dump.GET=* -xdebug.dump.POST=* -xdebug.dump.COOKIE=* -xdebug.dump.FILES=* -xdebug.dump.SESSION=* - -; ============================================================================ -; PROFILING (disabled by default) -; ============================================================================ -; Uncomment to enable profiling -; xdebug.profiler_enable=0 -; xdebug.profiler_enable_trigger=1 -; xdebug.profiler_enable_trigger_value="" -; xdebug.profiler_output_dir=/var/www/profiler -; xdebug.profiler_output_name=cachegrind.out.%p - -; ============================================================================ -; TRACING (disabled by default) -; ============================================================================ -; Uncomment to enable function tracing -; xdebug.trace_enable_trigger=1 -; xdebug.trace_enable_trigger_value="" -; xdebug.trace_output_dir=/var/www/traces -; xdebug.trace_output_name=trace.%c -; xdebug.trace_format=0 -; xdebug.trace_options=0 - -; ============================================================================ -; PERFORMANCE -; ============================================================================ -; Show memory usage in stack traces -xdebug.show_mem_delta=1 - -; ============================================================================ -; DISPLAY -; ============================================================================ -; HTML error output formatting -xdebug.cli_color=1 - -; Variable display depth -xdebug.var_display_max_depth=10 - -; Maximum number of array children/object properties -xdebug.var_display_max_children=256 - -; Maximum string length -xdebug.var_display_max_data=4096 - -; ============================================================================ -; USAGE TIPS -; ============================================================================ -; -; Enable Xdebug: -; make xdebug-on -; -; Disable Xdebug: -; make xdebug-off -; -; Check Status: -; make xdebug-status -; -; IDE Configuration: -; PHPStorm: -; - Settings > PHP > Debug -; - Port: 9003 -; - Check "Accept external connections" -; - Settings > PHP > Servers -; - Add server: localhost, port 9003 -; - Map: /var/www -> your-project-path -; -; VSCode: -; - Install PHP Debug extension -; - Add to launch.json: -; { -; "name": "Listen for Xdebug", -; "type": "php", -; "request": "launch", -; "port": 9003, -; "pathMappings": { -; "/var/www": "${workspaceFolder}" -; } -; } -; -; ============================================================================ \ No newline at end of file diff --git a/.config/phpmd/ruleset.xml b/.config/phpmd/ruleset.xml deleted file mode 100644 index 0c05d4d..0000000 --- a/.config/phpmd/ruleset.xml +++ /dev/null @@ -1,273 +0,0 @@ - - - - - - Professional PHPMD ruleset for KaririCode Framework components. - Enforces clean code principles, SOLID design, and maintainability. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */tests/* - */Tests/* - - - */vendor/* - - - */cache/* - */storage/* - */temp/* - */tmp/* - - - */build/* - */coverage/* - */docs/* - - - */migrations/* - */database/factories/* - */database/seeders/* - - - */_ide_helper*.php - *.blade.php - - - */config/* - - - */public/* - - - - \ No newline at end of file diff --git a/.docs/MAKEFILE-compose.md b/.docs/MAKEFILE-compose.md deleted file mode 100644 index 4bf200f..0000000 --- a/.docs/MAKEFILE-compose.md +++ /dev/null @@ -1,1683 +0,0 @@ -
- -# Docker Compose Management - -[![KaririCode](https://img.shields.io/badge/KaririCode-DevKit-6D00CC?style=for-the-badge)](https://github.com/KaririCode-Framework/kariricode-devkit) -[![Docker Compose](https://img.shields.io/badge/Docker%20Compose-Full%20Stack-2496ED?style=for-the-badge&logo=docker)](https://docs.docker.com/compose/) -[![Services](https://img.shields.io/badge/Services-PHP%20%7C%20Redis%20%7C%20Memcached-00C853?style=for-the-badge)](https://github.com/KaririCode-Framework/kariricode-devkit) - -**kariricode/devkit** - Professional development environment for KaririCode Framework - -Part of the [KaririCode Framework](https://kariricode.org) ecosystem - -[Main Documentation](MAKEFILE.md) | [GitHub Repository](https://github.com/KaririCode-Framework/kariricode-devkit) - -
- ---- - -## Table of Contents - -1. [Overview](#overview) -2. [Environment Setup](#environment-setup) -3. [Port Conflict Detection & Resolution](#port-conflict-detection--resolution) -4. [Lifecycle Management](#lifecycle-management) -5. [Service Monitoring](#service-monitoring) -6. [Container Interaction](#container-interaction) -7. [Configuration Management](#configuration-management) -8. [Troubleshooting](#troubleshooting) -9. [Advanced Workflows](#advanced-workflows) -10. [Production Considerations](#production-considerations) - ---- - -## Overview - -### Architecture -``` -┌───────────────────────────────────────────┐ -│ Docker Compose Stack │ -├───────────────────────────────────────────┤ -│ │ -│ ┌─────────────┐ ┌──────────────┐ │ -│ │ PHP-FPM + │◄────►│ Memcached │ │ -│ │ Nginx + │ │ (11211) │ │ -│ │ Redis │ └──────────────┘ │ -│ │ (80, 6379) │ │ -│ └─────────────┘ │ -│ │ │ -│ │ Volume Mount │ -│ ▼ │ -│ /var/www/html ◄────► ./ │ -│ │ -└───────────────────────────────────────────┘ - │ - │ Bridge Network - │ kariricode_network - │ - ┌────▼────┐ - │ Host │ - │ Machine │ - └─────────┘ -``` - -### Services - -| Service | Image | Purpose | Ports | Health Check | -|---------|-------|---------|-------|--------------| -| **php** | kariricode/php-api-stack:dev | Application runtime | 80, 6379 | Process check | -| **memcached** | memcached:1.6-alpine | Memory caching | 11211 | netcat probe | - -### Features - -✅ **Live Code Sync**: Volume mounting for instant updates -✅ **Isolated Network**: Bridge network for service communication -✅ **Health Monitoring**: Built-in health checks -✅ **Xdebug Support**: Configurable debugging -✅ **Auto-restart**: Service recovery on failure -✅ **Environment Variables**: Flexible configuration via `.env` -✅ **Port Conflict Detection**: Automatic detection and resolution - ---- - -## Environment Setup - -### Initial Configuration - -#### Step 1: Create Environment File -```bash -# Auto-created on first 'make up', or manually: -cp .env.example .env -``` - -#### Step 2: Configure Variables -```bash -# Edit .env -nano .env -``` - -**Key Configuration Variables:** -```bash -# Application -APP_NAME=kariricode-devkit -APP_ENV=development -APP_DEBUG=true -APP_PORT=8089 # Host port → container:80 - -# Demo & Health Check -DEMO_MODE=false -HEALTH_CHECK_INSTALL=true - -# Cache Services -REDIS_PORT=6379 # Host port → container:6379 -MEMCACHED_PORT=11211 # Host port → container:11211 - -# Xdebug -XDEBUG_MODE=off # off/debug/coverage/profile -XDEBUG_CLIENT_HOST=host.docker.internal - -# Composer -COMPOSER_MEMORY_LIMIT=-1 -``` - -#### Step 3: Verify Configuration -```bash -# Check .env file -make env-check - -# Output: -# ╔════════════════════════════════════════════════════════╗ -# ✓ .env file exists -# APP_NAME=kariricode-devkit -# APP_PORT=8089 -# REDIS_PORT=6379 -# ... -# ╚════════════════════════════════════════════════════════╝ -``` - ---- - -## Port Conflict Detection & Resolution - -### Overview - -**The Problem**: Port conflicts prevent Docker containers from starting, causing cryptic error messages like: -``` -Error: failed to bind host port for 0.0.0.0:11211:172.20.0.2:11211/tcp: address already in use -``` - -**The Solution**: Automated detection and resolution tools that identify conflicting processes and free up ports. - ---- - -### Quick Start - -#### Safe Startup (Recommended) -```bash -# Automatically checks ports before starting -make up-safe - -# Workflow: -# 1. Cleans orphaned Docker containers -# 2. Checks for Docker port conflicts -# 3. Checks for system port conflicts -# 4. Starts services if all clear -# 5. Shows status - -# If conflicts detected: -# ✗ Port 6379 in use by system process PID 1234 (redis-server) -# -# Resolution options: -# 1. Run: make diagnose-ports for detailed info -# 2. Run: make fix-ports to auto-resolve -# 3. Run: make kill-port PORT=6379 for specific port -``` - ---- - -### Port Conflict Commands - -#### 1. Check Ports -```bash -# Quick check for conflicts -make check-ports - -# What it does: -# 1. Cleans orphaned Docker containers -# 2. Checks Docker containers for port usage -# 3. Checks system processes for port usage -# 4. Reports status - -# Success output: -# → Checking system ports for conflicts... -# ✓ Port 8089 is available -# ✓ Port 6379 is available -# ✓ Port 11211 is available -# ✓ All ports available - -# Failure output: -# ✗ Port 6379 in use by system process PID 1234 (redis-server) -# -# Resolution options: -# 1. Run: make diagnose-ports for detailed info -# 2. Run: make fix-ports to auto-resolve -# 3. Run: make kill-port PORT=6379 for specific port -``` - -#### 2. Diagnose Ports -```bash -# Detailed port conflict analysis -make diagnose-ports - -# Output example: -# Port Conflict Diagnosis -# ╔════════════════════════════════════════════════════════╗ -# -# Required Ports: -# APP_PORT: 8089 -# REDIS_PORT: 6379 -# MEMCACHED_PORT: 11211 -# -# System Port Status: -# -# Port 8089: -# Status: AVAILABLE -# -# Port 6379: -# PID: 1234 | Command: redis-server | User: redis -# Status: IN USE (lsof) -# To kill: make kill-port PORT=6379 -# Or stop service: sudo systemctl stop redis -# -# Port 11211: -# PID: 5678 | Command: memcached | User: memcache -# Status: IN USE (ss) -# To kill: make kill-port PORT=11211 -# Or stop service: sudo systemctl stop memcached -# -# Docker Containers (All): -# None found -# -# ╚════════════════════════════════════════════════════════╝ -# -# Suggested Actions: -# 1. make fix-ports - Auto-kill conflicting processes -# 2. make kill-port PORT= - Kill specific process -# 3. sudo systemctl stop redis - Stop Redis service -# 4. sudo systemctl stop memcached - Stop Memcached service -# 5. Edit .env to use different ports: -# REDIS_PORT=6380 -# MEMCACHED_PORT=11212 -``` - -#### 3. Fix Ports (Automatic Resolution) -```bash -# Interactive automatic fix -make fix-ports - -# Workflow: -# ⚠ This will attempt to free up conflicting ports -# Docker containers and system processes will be terminated -# -# Continue? [y/N] y -# -# → Cleaning orphaned Docker resources... -# ✓ Docker cleanup complete -# -# → Scanning and fixing system port conflicts... -# → Terminating redis-server (PID 1234) on port 6379... -# ✓ Port 6379 freed -# → Terminating memcached (PID 5678) on port 11211... -# ✓ Port 11211 freed -# -# → Checking system ports for conflicts... -# ✓ Port 8089 is available -# ✓ Port 6379 is available -# ✓ Port 11211 is available -# ✓ All ports available -``` - -**What `fix-ports` does:** -1. Cleans orphaned Docker containers -2. Identifies processes using required ports -3. Attempts graceful shutdown (SIGTERM) -4. Forces shutdown if needed (SIGKILL) -5. Verifies ports are freed -6. Re-checks all ports - -**Safety Features:** -- ✅ Interactive confirmation required -- ✅ Graceful shutdown first (15 seconds wait) -- ✅ Force only if graceful fails -- ✅ Verifies success after each action -- ✅ Final check confirms all ports clear - -#### 4. Kill Specific Port -```bash -# Kill process on specific port -make kill-port PORT=6379 - -# Workflow: -# → Checking port 6379... -# Found process: PID 1234 (redis-server) -# Attempting graceful shutdown... -# ✓ Port 6379 is now free - -# For stubborn processes: -# Process didn't stop, forcing shutdown... -# ✓ Port 6379 is now free -``` - -**Use Cases:** -- Kill specific conflicting process -- Don't want to touch other ports -- Selective cleanup - -#### 5. Port Scanner -```bash -# Scan common ports for conflicts -make port-scan - -# Output: -# Port Scanner -# ╔════════════════════════════════════════════════════════╗ -# -# ✓ Port 80: Available -# ✗ Port 443: IN USE (PID 890 - nginx) -# ✓ Port 3000: Available -# ✗ Port 3306: IN USE (PID 1122 - mysqld) -# ✓ Port 5432: Available -# ✗ Port 6379: IN USE (PID 1234 - redis-server) -# ✓ Port 8000: Available -# ✓ Port 8080: Available -# ✓ Port 8089: Available -# ✓ Port 9000: Available -# ✗ Port 11211: IN USE (PID 5678 - memcached) -# ✓ Port 27017: Available -# -# ╚════════════════════════════════════════════════════════╝ -``` - -**Use Cases:** -- Quick overview of port availability -- Planning port assignments -- Identifying system services -- Troubleshooting network issues - -#### 6. Clean Docker Resources -```bash -# Remove orphaned containers and networks -make clean-docker - -# Output: -# → Cleaning orphaned Docker resources... -# Removing stopped containers... -# Deleted Containers: -# a9d5c257ff5f -# 34704960e194 -# -# Removing unused networks... -# Deleted Networks: -# kariricode-devkit_network -# -# ✓ Docker cleanup complete -``` - -**When to use:** -- Before starting services -- After failed `docker compose up` -- When seeing "address already in use" errors -- Regular maintenance - ---- - -### Port Conflict Workflows - -#### Workflow 1: First Time Setup -```bash -# 1. Clone repository -git clone https://github.com/KaririCode-Framework/kariricode-devkit.git -cd kariricode-devkit - -# 2. Create .env -cp .env.example .env - -# 3. Check for conflicts -make check-ports - -# If conflicts found: -# 4. View detailed info -make diagnose-ports - -# 5. Resolve conflicts -make fix-ports - -# 6. Start services safely -make up-safe -``` - -#### Workflow 2: Port Already in Use Error -```bash -# Scenario: You tried 'make up' and got: -# Error: address already in use - -# 1. Diagnose the problem -make diagnose-ports - -# 2. Option A: Kill conflicting processes -make fix-ports - -# 3. Option B: Change ports in .env -nano .env -# Change: REDIS_PORT=6380 -# MEMCACHED_PORT=11212 - -# 4. Retry startup -make up-safe -``` - -#### Workflow 3: System Service Conflicts -```bash -# Scenario: Local Redis/Memcached running - -# 1. Identify services -make diagnose-ports -# → Redis (6379): PID 1234 -# → Memcached (11211): PID 5678 - -# 2. Option A: Stop system services -sudo systemctl stop redis -sudo systemctl stop memcached - -# 3. Option B: Disable autostart -sudo systemctl disable redis -sudo systemctl disable memcached - -# 4. Option C: Use different ports -echo "REDIS_PORT=6380" >> .env -echo "MEMCACHED_PORT=11212" >> .env - -# 5. Start Docker services -make up-safe -``` - -#### Workflow 4: Orphaned Docker Containers -```bash -# Scenario: Previous containers not cleaned up - -# 1. Clean Docker resources -make clean-docker - -# 2. Verify cleanup -docker ps -a -# Should show no kariricode-devkit containers - -# 3. Check ports again -make check-ports - -# 4. Start fresh -make up -``` - -#### Workflow 5: Multiple Developers -```bash -# Scenario: Different developers, different ports - -# Developer A (.env) -APP_PORT=8089 -REDIS_PORT=6379 -MEMCACHED_PORT=11211 - -# Developer B (.env) - all different -APP_PORT=8090 -REDIS_PORT=6380 -MEMCACHED_PORT=11212 - -# Each developer: -make check-ports # Verify their ports are free -make up-safe # Start with their configuration -``` - ---- - -### Common Port Conflict Scenarios - -#### Scenario 1: Local Redis Running -**Problem:** -``` -✗ Port 6379 in use by system process PID 1234 (redis-server) -``` - -**Solutions:** - -**Option A: Stop system Redis** -```bash -# Temporary stop -sudo systemctl stop redis - -# Permanent disable -sudo systemctl disable redis - -# Start Docker services -make up-safe -``` - -**Option B: Use different port** -```bash -# Change Docker port -echo "REDIS_PORT=6380" >> .env - -# Restart -make up-safe - -# Access Redis on new port -redis-cli -h localhost -p 6380 -``` - -**Option C: Kill process** -```bash -make kill-port PORT=6379 -make up-safe -``` - -#### Scenario 2: Local Memcached Running -**Problem:** -``` -✗ Port 11211 in use by system process PID 5678 (memcached) -``` - -**Solutions:** - -**Option A: Stop system Memcached** -```bash -sudo systemctl stop memcached -make up-safe -``` - -**Option B: Change port** -```bash -echo "MEMCACHED_PORT=11212" >> .env -make up-safe -``` - -#### Scenario 3: Orphaned Docker Containers -**Problem:** -``` -Error: failed to bind host port for 0.0.0.0:11211 -✓ Port 11211 is available (via lsof) # Confusing! -``` - -**Cause:** Docker containers in "Created" or "Exited" state still hold port bindings - -**Solution:** -```bash -# Clean orphaned containers -make clean-docker - -# Verify cleanup -docker ps -a | grep kariricode -# Should be empty - -# Start fresh -make up -``` - -#### Scenario 4: Multiple Projects -**Problem:** Multiple projects using same ports - -**Solution 1: Port Namespacing** -```bash -# Project A -APP_PORT=8089 -REDIS_PORT=6379 -MEMCACHED_PORT=11211 - -# Project B -APP_PORT=8189 -REDIS_PORT=6479 -MEMCACHED_PORT=11311 - -# Project C -APP_PORT=8289 -REDIS_PORT=6579 -MEMCACHED_PORT=11411 -``` - -**Solution 2: Stop Other Projects** -```bash -# In other project directories -make down - -# In current project -make up-safe -``` - ---- - -### Troubleshooting Port Issues - -#### Issue: "Permission Denied" When Killing Process - -**Symptoms:** -``` -✗ Failed to free port 6379 (may require sudo) -``` - -**Solution:** -```bash -# Option A: Use sudo -sudo make kill-port PORT=6379 - -# Option B: Stop service properly -sudo systemctl stop redis - -# Option C: Change port -echo "REDIS_PORT=6380" >> .env -``` - -#### Issue: Port Shows Available But Still Fails - -**Diagnosis:** -```bash -# Check with multiple tools -lsof -i :6379 -ss -ltn | grep :6379 -netstat -tlnp | grep :6379 - -# Check Docker specifically -docker ps -a --format "{{.Names}}\t{{.Ports}}" | grep 6379 -``` - -**Solution:** -```bash -# Clean everything -make clean-docker -docker system prune -f - -# Verify -make check-ports - -# Start fresh -make up -``` - -#### Issue: Ports Freed But Container Won't Start - -**Diagnosis:** -```bash -# Check detailed error -make up -make logs - -# Common causes: -# - Image not found -# - Invalid .env syntax -# - Volume permission issues -``` - -**Solution:** -```bash -# Pull latest image -docker pull kariricode/php-api-stack:dev - -# Validate .env -make env-check - -# Fix permissions -sudo chown -R $USER:$USER . - -# Retry -make up-safe -``` - ---- - -### Best Practices - -#### 1. Always Use `up-safe` for First Start -```bash -# ✅ Good: Checks ports first -make up-safe - -# ❌ Avoid: May fail with cryptic errors -make up -``` - -#### 2. Regular Cleanup -```bash -# Weekly maintenance -make clean-docker -make check-ports - -# After failed starts -make clean-docker -make up-safe -``` - -#### 3. Port Reservation Strategy -```bash -# .env - Use port ranges per project -# Project A: 808X, 637X, 1121X -APP_PORT=8089 -REDIS_PORT=6379 -MEMCACHED_PORT=11211 - -# Project B: 818X, 647X, 1131X -APP_PORT=8189 -REDIS_PORT=6479 -MEMCACHED_PORT=11311 -``` - -#### 4. Document Custom Ports -```bash -# .env -# Custom ports to avoid conflict with local Redis -REDIS_PORT=6380 # Changed from 6379 -MEMCACHED_PORT=11212 # Changed from 11211 -``` - -#### 5. Automated Conflict Resolution in Scripts -```bash -#!/bin/bash -# start-dev.sh - -# Ensure ports are free -make check-ports || make fix-ports - -# Start services -make up-safe - -# Wait for healthy -until make health | grep -q "healthy"; do - echo "Waiting for services..." - sleep 2 -done - -echo "✓ Development environment ready" -``` - ---- - -## Lifecycle Management - -### Starting Services - -#### Standard Start -```bash -# Start all services in detached mode -make up - -# What happens: -# 1. Checks for .env (creates if missing) -# 2. Pulls images if needed -# 3. Creates network -# 4. Starts containers -# 5. Shows status - -# Output: -# ✓ Services started -# ╔════════════════════════════════════════════════════════╗ -# NAME STATUS PORTS -# kariricode-devkit_php Up 5 seconds 0.0.0.0:8089->80/tcp -# kariricode-devkit_memcached Up 5 seconds 0.0.0.0:11211->11211/tcp -# ╚════════════════════════════════════════════════════════╝ -``` - -#### Safe Start (Recommended) -```bash -# Start with port conflict detection -make up-safe - -# Executes: -# 1. make check-ports (cleans Docker, checks ports) -# 2. make up (starts services) - -# Use this for: -# - First time setup -# - After system reboot -# - When unsure about port conflicts -# - Team onboarding -``` - -#### Start with Build -```bash -# Force rebuild images before starting -make up-build - -# Use cases: -# - Dockerfile changes -# - Base image updates -# - Fresh start needed -``` - -### Stopping Services -```bash -# Stop and remove containers (preserves volumes) -make down -# ✓ Services stopped and removed - -# Stop without removing -make stop -# Containers remain, can be started with 'make start' - -# Start existing stopped containers -make start -``` - -### Restarting Services -```bash -# Restart all services -make restart - -# Equivalent to: -# docker compose restart - -# Use cases: -# - Configuration changes in docker-compose.yml -# - Memory leaks -# - Service recovery -# - .env changes (some variables) -``` - -### Complete Rebuild -```bash -# Nuclear option: destroy and recreate everything -make rebuild - -# Executes: -# 1. make down → Stop and remove containers -# 2. make clean-volumes → Delete ALL volumes -# 3. make up-build → Rebuild images and start - -# ⚠️ WARNING: This destroys ALL data! -# Are you sure? [y/N] -``` - ---- - -## Service Monitoring - -### Status Checks - -#### Current Status -```bash -# Show service status -make status -make ps # Alias - -# Output: -# ╔════════════════════════════════════════════════════════╗ -# NAME STATE STATUS -# kariricode-devkit_php Up healthy -# kariricode-devkit_memcached Up healthy -# ╚════════════════════════════════════════════════════════╝ -``` - -#### Health Checks -```bash -# Detailed health status -make health - -# Output shows: -# kariricode-devkit_php: healthy - Up 2 minutes -# kariricode-devkit_memcached: healthy - Up 2 minutes - -# Health check definitions: -# - Memcached: nc -z 127.0.0.1 11211 (every 10s) -# - PHP: Process running check -``` - -### Logs - -#### View Logs -```bash -# All services -make logs - -# Specific service -make logs SERVICE=php -make logs SERVICE=memcached - -# Example output: -# kariricode-devkit_php | [15-Jan-2025 10:30:00] NOTICE: fpm is running -# kariricode-devkit_memcached | <5 new connections -``` - -#### Follow Logs (Real-time) -```bash -# Tail all logs -make logs-follow - -# Tail specific service -make logs-follow SERVICE=php - -# Press Ctrl+C to stop following -``` - -**Log Use Cases:** - -| Scenario | Command | What to Look For | -|----------|---------|------------------| -| Application errors | `make logs-follow SERVICE=php` | PHP errors, warnings | -| Service crashes | `make logs` | Exit codes, error messages | -| Performance issues | `make logs SERVICE=php` | Slow query logs, timeouts | -| Cache debugging | `make logs SERVICE=memcached` | Connection stats, evictions | - -### Port Mappings -```bash -# Show exposed ports -make ports - -# Output: -# ╔════════════════════════════════════════════════════════╗ -# NAME PORTS -# *_php 0.0.0.0:8089->80/tcp, :::6379->6379/tcp -# *_memcached 0.0.0.0:11211->11211/tcp -# ╚════════════════════════════════════════════════════════╝ -``` - -**Access Services:** -```bash -# Application HTTP -curl http://localhost:8089 - -# Redis (from host, if exposed) -redis-cli -h localhost -p 6379 - -# Memcached (from host) -echo "stats" | nc localhost 11211 -``` - ---- - -## Container Interaction - -### PHP Container - -#### Interactive Shell -```bash -# Open bash shell -make exec-php -make shell # Alias - -# Inside container: -root@abc123:/var/www/html# php -v -root@abc123:/var/www/html# composer --version -root@abc123:/var/www/html# ls -la -``` - -#### Execute Commands -```bash -# Single command execution -make exec-php CMD="php -v" -# PHP 8.4.14 (cli) (built: Jan 15 2025) (NTS) - -make exec-php CMD="composer --version" -# Composer version 2.8.4 - -make exec-php CMD="php artisan migrate" -make exec-php CMD="./bin/console cache:clear" -``` - -**Common Tasks:** -```bash -# Check PHP modules -make exec-php CMD="php -m" - -# Test Redis connection -make exec-php CMD="php -r 'new Redis();'" - -# View logs -make exec-php CMD="tail -f var/log/app.log" - -# Run application commands -make exec-php CMD="make test" -``` - -### Memcached Container -```bash -# Connect to Memcached container -make exec-memcached - -# Inside container: -/ # echo "stats" | nc localhost 11211 -STAT pid 1 -STAT uptime 3600 -STAT curr_connections 5 -... - -/ # echo "flush_all" | nc localhost 11211 -OK -``` - ---- - -## Configuration Management - -### Validate Configuration - -#### Syntax Validation -```bash -# Check docker-compose.yml syntax -make validate-compose - -# Output: -# ✓ docker-compose.yml is valid - -# Errors show line numbers and details -``` - -#### View Resolved Configuration -```bash -# Show merged configuration -make config - -# Output includes: -# - Environment variable substitution -# - Service definitions -# - Network configuration -# - Volume mounts -# - All resolved values - -# Useful for debugging: -# - Environment variable issues -# - Configuration inheritance -# - Overrides from .env -``` - -### Environment Verification -```bash -# Check .env file status -make env-check - -# Shows: -# 1. File existence -# 2. First 20 variables -# 3. Validation warnings - -# If missing: -# ✗ .env file not found -# Run: cp .env.example .env -``` - -### Network Inspection -```bash -# Inspect Docker network -make network-inspect - -# Output: -# ╔════════════════════════════════════════════════════════╗ -# [ -# { -# "Name": "kariricode-devkit_network", -# "Driver": "bridge", -# "Scope": "local", -# "Subnet": "172.20.0.0/16", -# "Gateway": "172.20.0.1", -# "Containers": { -# "php": {"IPv4Address": "172.20.0.2"}, -# "memcached": {"IPv4Address": "172.20.0.3"} -# } -# } -# ] -# ╚════════════════════════════════════════════════════════╝ -``` - ---- - -## Composer Integration - -### Install Dependencies -```bash -# Install from composer.lock -make composer-install - -# Executes in PHP container: -# composer install --no-interaction --prefer-dist --optimize-autoloader -``` - -### Update Dependencies -```bash -# Update all dependencies -make composer-update - -# Executes: -# composer update --with-all-dependencies --no-interaction --prefer-dist --optimize-autoloader -``` - -**Advantages of Container Execution:** -- ✅ No local PHP version conflicts -- ✅ Consistent dependencies across team -- ✅ Matches production environment -- ✅ Isolated from host system - ---- - -## Troubleshooting - -### Port-Related Issues - -See complete port troubleshooting in [Port Conflict Detection & Resolution](#port-conflict-detection--resolution) section. - -### Issue 1: Services Won't Start - -**Symptoms:** -- Container exits immediately -- `make up` fails - -**Diagnosis:** -```bash -# Check logs -make logs - -# Common error messages: -# - "Address already in use" → Port conflict -# - "Invalid reference format" → .env syntax error -# - "Pull access denied" → Image not found -``` - -**Solutions:** -```bash -# Port conflict -make diagnose-ports -make fix-ports - -# Invalid .env -make env-check -# Fix syntax errors - -# Missing image -docker pull kariricode/php-api-stack:dev -``` - -### Issue 2: Can't Connect to Services - -**Symptoms:** -- `Connection refused` -- `curl: (7) Failed to connect` - -**Diagnosis:** -```bash -# Check if services are running -make status - -# Check ports -make ports - -# Check network -make network-inspect -``` - -**Solutions:** -```bash -# Service not running -make restart - -# Wrong port -curl http://localhost:$(grep APP_PORT .env | cut -d= -f2) - -# Network issue -make down -docker network prune -make up-safe -``` - -### Issue 3: Permission Denied - -**Symptoms:** -- `mkdir: cannot create directory: Permission denied` -- `composer: Failed to download` - -**Diagnosis:** -```bash -# Check file ownership -make exec-php CMD="ls -la /var/www/html" -``` - -**Solution:** -```bash -# Fix ownership on host -sudo chown -R $USER:$USER . - -# Or fix inside container -make exec-php CMD="chown -R www-data:www-data /var/www/html" - -# Restart -make restart -``` - -### Issue 4: Volume Data Corruption - -**Symptoms:** -- Stale data -- Inconsistent state -- Old files persist - -**Solution:** -```bash -# Nuclear option: destroy volumes -make clean-volumes - -# ⚠️ WARNING: Confirmation required -# This will delete ALL volume data! -# Are you sure? [y/N] y - -# ✓ Volumes removed - -# Rebuild -make up-build -``` - -### Issue 5: Xdebug Not Working - -**Diagnosis:** -```bash -# Check Xdebug is loaded -make exec-php CMD="php -v" -# Should show: with Xdebug v3.x - -# Check mode -make exec-php CMD="php -r 'echo ini_get(\"xdebug.mode\");'" -``` - -**Solution:** -```bash -# Enable in .env -sed -i 's/XDEBUG_MODE=off/XDEBUG_MODE=debug/' .env - -# Restart services -make restart - -# Verify -make exec-php CMD="php -m | grep xdebug" -``` - ---- - -## Advanced Workflows - -### Development with Live Reload -```bash -# 1. Start services -make up-safe - -# 2. Open separate terminal for logs -make logs-follow SERVICE=php - -# 3. Edit code in your IDE -# Changes are immediately visible (volume mount) - -# 4. Test changes -curl http://localhost:8089/health - -# 5. No restart needed! -``` - -### Multi-Environment Configuration - -#### Development Environment -```bash -# Use .env.development -cp .env.development .env -make up-safe - -# Characteristics: -# - XDEBUG_MODE=debug -# - APP_DEBUG=true -# - Verbose logging -``` - -#### Testing Environment -```bash -# Use .env.testing -cp .env.testing .env -make rebuild - -# Characteristics: -# - XDEBUG_MODE=coverage -# - Clean state -# - Test database -``` - -#### Production Simulation -```bash -# Use .env.production -cp .env.production .env -make up-build - -# Characteristics: -# - XDEBUG_MODE=off -# - APP_DEBUG=false -# - Optimized settings -``` - -### Debugging Workflows - -#### Xdebug with IDE - -**Step 1: Configure .env** -```bash -echo "XDEBUG_MODE=debug" > .env -echo "XDEBUG_CLIENT_HOST=host.docker.internal" >> .env -make restart -``` - -**Step 2: IDE Configuration (PHPStorm)** -``` -Settings → PHP → Servers: -- Name: kariricode-devkit -- Host: localhost -- Port: 8089 -- Path mappings: ./ → /var/www/html -``` - -**Step 3: Start Listening** -``` -PHPStorm: Run → Start Listening for PHP Debug Connections -``` - -**Step 4: Trigger Breakpoint** -```bash -curl http://localhost:8089/debug -``` - -#### Performance Profiling -```bash -# Enable profiling -sed -i 's/XDEBUG_MODE=off/XDEBUG_MODE=profile/' .env -make restart - -# Run application -curl http://localhost:8089/api/endpoint - -# View profile files -make exec-php CMD="ls -lh /tmp/xdebug" -# cachegrind.out.XXXX files - -# Download for analysis -docker cp kariricode-devkit_php:/tmp/xdebug/cachegrind.out.1234 ./ -``` - -**Analyze with:** -- **KCacheGrind** (Linux) -- **QCacheGrind** (macOS/Windows) -- **Webgrind** (Web-based) - -### Integration Testing Workflow -```bash -# 1. Start full stack -make up-safe - -# 2. Wait for services -sleep 3 - -# 3. Run integration tests -make test-compose - -# Workflow in make test-compose: -# - Verifies services are up -# - Executes: docker compose exec php make test -# - Returns test exit code - -# 4. View logs on failure -make logs - -# 5. Cleanup -make down -``` - ---- - -## Production Considerations - -### ⚠️ Development Only - -This Docker Compose setup is **NOT production-ready**. It's designed for: -- ✅ Local development -- ✅ Integration testing -- ✅ Rapid prototyping -- ✅ Team onboarding - -### Production Limitations - -| Concern | Development Setup | Production Requirement | -|---------|-------------------|------------------------| -| **SSL/TLS** | No HTTPS | Terminate SSL at load balancer | -| **Secrets** | .env file | Vault/Secrets Manager | -| **Scaling** | Single container | Horizontal scaling, orchestration | -| **Monitoring** | Docker logs | Prometheus, Grafana, ELK | -| **High Availability** | No redundancy | Multi-node, failover | -| **Resource Limits** | Unlimited | CPU/Memory constraints | -| **Backup** | No strategy | Automated backups, replication | -| **Security** | Debug enabled | Hardened images, least privilege | - -### Production Recommendations - -**For Production Deployment:** - -1. **Use Orchestration Platforms** -``` - - Kubernetes (EKS, GKE, AKS) - - Docker Swarm (simple cases) - - AWS ECS/Fargate -``` - -2. **Separate Stateful Services** -``` - - Managed Redis (AWS ElastiCache, Redis Cloud) - - Managed Memcached - - External databases -``` - -3. **Implement Security Best Practices** -``` - - Run as non-root user - - Scan images for vulnerabilities - - Use minimal base images (Alpine) - - Regular security updates -``` - -4. **Add Monitoring & Logging** -``` - - Centralized logging (ELK, Loki) - - Metrics collection (Prometheus) - - Distributed tracing (Jaeger) - - Alerting (PagerDuty, Opsgenie) -``` - -5. **Implement CI/CD** -``` - - Automated testing - - Image scanning - - Deployment automation - - Rollback capabilities -``` - ---- - -## CI/CD Integration - -### GitHub Actions Example -```yaml -name: Integration Tests - -on: [push, pull_request] - -jobs: - test: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Create .env - run: cp .env.example .env - - - name: Check for port conflicts - run: make check-ports - - - name: Start Docker Compose - run: make up-safe - - - name: Wait for services - run: sleep 5 - - - name: Run tests - run: make test-compose - - - name: Show logs on failure - if: failure() - run: make logs - - - name: Cleanup - if: always() - run: make down -``` - -### GitLab CI Example -```yaml -integration-tests: - stage: test - image: docker:latest - services: - - docker:dind - - variables: - DOCKER_DRIVER: overlay2 - - before_script: - - apk add --no-cache make lsof - - cp .env.example .env - - make clean-docker - - make check-ports - - make up-safe - - script: - - make test-compose - - after_script: - - make logs - - make down - - artifacts: - when: always - reports: - junit: build/reports/junit.xml - paths: - - coverage/ -``` - ---- - -## Command Reference - -### Port Management -```bash -make check-ports # Check for port conflicts -make diagnose-ports # Detailed port analysis -make fix-ports # Auto-fix port conflicts (interactive) -make kill-port PORT=X # Kill specific port -make port-scan # Scan common ports -make clean-docker # Clean orphaned containers -make up-safe # Safe startup with port check -``` - -### Lifecycle -```bash -make up # Start services -make up-build # Start with build -make down # Stop and remove -make stop # Stop (preserve containers) -make start # Start existing containers -make restart # Restart services -make rebuild # Complete rebuild -``` - -### Monitoring -```bash -make status # Service status -make ps # Alias for status -make health # Health checks -make logs # View logs -make logs-follow # Follow logs (real-time) -make ports # Show port mappings -``` - -### Interaction -```bash -make exec-php # PHP shell -make shell # Alias for exec-php -make exec-memcached # Memcached shell -make composer-install # Install dependencies -make composer-update # Update dependencies -make test-compose # Run tests in compose -``` - -### Configuration -```bash -make config # View resolved config -make validate-compose # Validate syntax -make env-check # Check .env file -make network-inspect # Inspect network -make inspect-php # PHP container details -``` - -### Cleanup -```bash -make prune # Remove unused resources -make clean-volumes # Delete volumes (destructive) -``` - ---- - -## Best Practices - -### 1. Use .env for Configuration -```bash -# ❌ Bad: Hardcode in docker-compose.yml -ports: - - "8089:80" - -# ✅ Good: Use environment variables -ports: - - "${APP_PORT:-8089}:80" -``` - -### 2. Always Check Ports Before Starting -```bash -# ✅ Recommended workflow -make check-ports && make up - -# ✅ Or use safe startup -make up-safe -``` - -### 3. Clean Up Regularly -```bash -# Weekly cleanup -make clean-docker -make prune - -# Before major changes -make rebuild -``` - -### 4. Monitor Health -```bash -# Add to daily routine -make health - -# Investigate unhealthy services immediately -make logs SERVICE= -``` - -### 5. Version Control -```bash -# ✅ Commit -- docker-compose.yml -- .env.example -- Makefile - -# ❌ Don't commit -- .env -- Volumes -- Build artifacts -``` - -### 6. Document Port Changes -```bash -# .env -# Custom ports to avoid local Redis conflict -REDIS_PORT=6380 # Changed from 6379 (local Redis running) -MEMCACHED_PORT=11212 # Changed from 11211 (local Memcached) -``` - ---- - -## Quick Reference Card -``` -╔═══════════════════════════════════════════════════════════╗ -║ Docker Compose Quick Reference ║ -╠═══════════════════════════════════════════════════════════╣ -║ SAFE START │ make up-safe ║ -║ START │ make up ║ -║ STOP │ make down ║ -║ RESTART │ make restart ║ -║ REBUILD │ make rebuild ║ -║──────────────┼────────────────────────────────────────────║ -║ CHECK PORTS │ make check-ports ║ -║ DIAGNOSE │ make diagnose-ports ║ -║ FIX PORTS │ make fix-ports ║ -║ KILL PORT │ make kill-port PORT=6379 ║ -║ PORT SCAN │ make port-scan ║ -║──────────────┼────────────────────────────────────────────║ -║ STATUS │ make status ║ -║ LOGS │ make logs-follow ║ -║ HEALTH │ make health ║ -║──────────────┼────────────────────────────────────────────║ -║ SHELL │ make shell ║ -║ COMMAND │ make exec-php CMD="php -v" ║ -║ COMPOSE TEST │ make test-compose ║ -║──────────────┼────────────────────────────────────────────║ -║ VALIDATE │ make validate-compose ║ -║ CHECK ENV │ make env-check ║ -║ INSPECT │ make inspect-php ║ -╚═══════════════════════════════════════════════════════════╝ - -Port Conflict Workflow: - 1. make diagnose-ports # Identify conflicts - 2. make fix-ports # Auto-resolve - 3. make up-safe # Start safely - -Or change ports: - echo "REDIS_PORT=6380" >> .env - echo "MEMCACHED_PORT=11212" >> .env - make up-safe -``` - ---- - -**Version**: 1.0.0 -**Module**: `Makefile.docker-compose.mk` -**Maintainer**: Walmir Silva \ No newline at end of file diff --git a/.docs/MAKEFILE-docker.md b/.docs/MAKEFILE-docker.md deleted file mode 100644 index dba34ce..0000000 --- a/.docs/MAKEFILE-docker.md +++ /dev/null @@ -1,821 +0,0 @@ -
- -# Docker Commands - -[![KaririCode](https://img.shields.io/badge/KaririCode-DevKit-6D00CC?style=for-the-badge)](https://github.com/KaririCode-Framework/kariricode-devkit) -[![Docker](https://img.shields.io/badge/Docker-Containerized-2496ED?style=for-the-badge&logo=docker)](https://www.docker.com) -[![PHP Stack](https://img.shields.io/badge/PHP%20Stack-8.4-777BB4?style=for-the-badge&logo=php)](https://hub.docker.com/r/kariricode/php-api-stack) - -**kariricode/devkit** - Professional development environment for KaririCode Framework - -Part of the [KaririCode Framework](https://kariricode.org) ecosystem - -[Main Documentation](MAKEFILE.md) | [GitHub Repository](https://github.com/KaririCode-Framework/kariricode-devkit) - -
- ---- - -## Table of Contents - -1. [Overview](#overview) -2. [Docker Infrastructure](#docker-infrastructure) -3. [Docker Core Commands](#docker-core-commands) -4. [Docker QA Pipeline](#docker-qa-pipeline) -5. [Docker Image Management](#docker-image-management) -6. [Docker Utility Tools](#docker-utility-tools) -7. [Troubleshooting](#troubleshooting) -8. [Best Practices](#best-practices) - ---- -## Overview - -### Scope - -The Docker modules provide containerized execution for: -- **Isolated Environment**: Consistent PHP 8.4 environment -- **CI/CD Simulation**: Match production conditions locally -- **Team Consistency**: Same environment across all developers -- **Clean State**: No local configuration interference - -### Module Architecture -```tree -. -├── .make/docker/ -│ ├── Makefile.docker-core.mk # → Core: Shell, Composer, PHP execution -│ ├── Makefile.docker-compose.mk # → Stack: Full stack management (up, down, logs) -│ ├── Makefile.docker-qa.mk # → QA: CI/CD pipeline in containers -│ ├── Makefile.docker-image.mk # → Image: Pull, info, clean -│ └── Makefile.docker-tools.mk # → Tools: htop, jq, vim, network diagnostics -│ -└── arfa-runtime-stack/docker/ - ├── entrypoint.sh # → Entrypoint: Container initialization script - ├── config-processor.sh # → Config: Processes PHP/Swoole configs from env vars - └── health-check.sh # → Health: Docker health check script -``` - -### Docker vs Local Execution - -| Aspect | Local | Docker | -|--------|-------|--------| -| **Speed** | ⚡ Fastest | 🐢 Slower (container overhead) | -| **Consistency** | ⚠️ Varies by environment | ✅ Always consistent | -| **IDE Integration** | ✅ Full support | ⚠️ Requires configuration | -| **Debugging** | ✅ Native Xdebug | ⚠️ Network setup needed | -| **CI/CD Match** | ❌ May differ | ✅ Identical | -| **Team Sync** | ❌ Varies | ✅ Identical | -| **Clean State** | ❌ Cached configs | ✅ Fresh each run | - -**When to Use Docker:** -- ✅ CI/CD pipelines (mandatory) -- ✅ Pre-commit final validation -- ✅ Testing specific PHP versions -- ✅ New team member onboarding -- ✅ Isolating global configuration - -**When to Use Local:** -- ✅ Daily development -- ✅ IDE integration (debugging, intellisense) -- ✅ Fast iteration cycles -- ✅ Custom Xdebug configuration - ---- - -## Docker Infrastructure - -### Container Image -```bash -# Image details -Image: kariricode/php-api-stack:dev -Base: php:8.4-fpm-alpine -Size: ~350MB -Registry: Docker Hub - -# Included tools: -- PHP 8.4.14 (CLI + FPM) -- Composer 2.8.4 -- Xdebug 3.x -- Git -- Make -- Common PHP extensions -``` - -### Volume Mounting -```bash -# Project directory mounted at: -Host: $(PWD) -Container: /var/www/html - -# Live sync: Changes immediately visible in container -``` - -### Network Configuration -```bash -# Default: Host network mode -- No port mapping needed for basic commands -- Container can access host services - -# Compose: Bridge network -- Isolated network for services -- See MAKEFILE-compose.md for details -``` - ---- - -## Docker Core Commands - -### Interactive Shell - -#### Open Bash Shell -```bash -make docker-shell - -# Output: -# → Opening Docker shell (kariricode/php-api-stack:dev)... -# -# root@abc123:/var/www/html# - -# Inside container: -root@abc123:/var/www/html# php -v -PHP 8.4.14 (cli) (built: Jan 15 2025) (NTS) - -root@abc123:/var/www/html# ls -la -total 120 -drwxr-xr-x 8 root root 4096 Jan 15 10:30 . -drwxr-xr-x 1 root root 4096 Jan 15 10:20 .. -drwxr-xr-x 3 root root 4096 Jan 15 10:25 src -drwxr-xr-x 3 root root 4096 Jan 15 10:25 tests --rw-r--r-- 1 root root 2456 Jan 15 10:22 composer.json -... - -root@abc123:/var/www/html# exit -``` - -#### Use Cases -```bash -# Explore environment -make docker-shell -> php -m # Check modules -> php -i | grep xdebug # Check Xdebug -> composer --version # Verify Composer - -# Debug issues -make docker-shell -> ls -la vendor/ # Check dependencies -> php -l src/File.php # Lint specific file - -# Run custom commands -make docker-shell -> phpunit --filter testSpecificMethod -> php bin/console custom:command -``` - -### Composer Commands -```bash -# Generic Composer execution -make docker-composer CMD="" - -# Examples: - -# Install dependencies -make docker-composer CMD="install" - -# Update dependencies -make docker-composer CMD="update" - -# Show installed packages -make docker-composer CMD="show --installed" - -# Require new package -make docker-composer CMD="require kariricode/new-component" - -# Remove package -make docker-composer CMD="remove vendor/package" - -# Validate composer.json -make docker-composer CMD="validate --strict" - -# Diagnose issues -make docker-composer CMD="diagnose" -``` - -### PHP Commands -```bash -# Generic PHP execution -make docker-php CMD="" - -# Examples: - -# Check PHP version -make docker-php CMD="-v" - -# Show PHP info -make docker-php CMD="-i" - -# List loaded modules -make docker-php CMD="-m" - -# Check configuration -make docker-php CMD="-i | grep xdebug" - -# Execute script -make docker-php CMD="script.php" - -# Evaluate expression -make docker-php CMD="-r 'echo PHP_VERSION;'" -``` - ---- - -## Docker QA Pipeline - -### Individual QA Commands - -All local QA commands have Docker equivalents: -```bash -# Testing -make docker-test # All tests -make docker-test-unit # Unit tests only -make docker-test-integration # Integration tests - -# Static Analysis -make docker-phpstan # PHPStan -make docker-psalm # Psalm -make docker-analyse # All analysis - -# Code Style -make docker-cs-check # Check style -make docker-format # Auto-fix style -make docker-lint # Syntax check - -# Coverage & Mutation -make docker-coverage # Code coverage -make docker-mutation # Mutation testing - -# Benchmarks -make docker-bench # Performance benchmarks -``` - -### Docker CI Pipelines - -#### Fast CI in Docker -```bash -make docker-ci - -# Executes in isolated container: -# ╔════════════════════════════════════════════════════════╗ -# ║ Docker CI Pipeline (Isolated Environment) ║ -# ╚════════════════════════════════════════════════════════╝ -# -# → Running make ci in Docker... -# [Complete CI pipeline output...] -# ✓ Docker make ci complete -# -# ✓ Docker CI pipeline completed - -# Advantages: -# - No local config interference -# - Same environment as production -# - Clean state every run -``` - -#### Full CI in Docker -```bash -make docker-ci-full - -# Complete quality validation in container: -# ╔════════════════════════════════════════════════════════╗ -# ║ Docker Full CI Pipeline (Isolated Environment) ║ -# ╚════════════════════════════════════════════════════════╝ -# -# → Running make ci-full in Docker... -# [Full CI pipeline with coverage & mutation...] -# ✓ Docker make ci-full complete -# -# ✓ Docker full CI pipeline completed -``` - -### Comparison: Local vs Docker CI -```bash -# Local CI (fast, uses your PHP config) -make ci -# Time: ~1-2 minutes -# Uses: Local PHP, local cache - -# Docker CI (isolated, production-like) -make docker-ci -# Time: ~2-3 minutes -# Uses: Container PHP, no cache interference - -# Use case: -# - Daily: make ci (fast feedback) -# - Pre-push: make docker-ci (final validation) -``` - ---- - -## Docker Image Management - -### Pull Image -```bash -make docker-pull - -# Downloads latest image from Docker Hub: -# → Pulling Docker image kariricode/php-api-stack:dev... -# dev: Pulling from kariricode/php-api-stack -# a1234567890b: Pull complete -# b2345678901c: Pull complete -# ... -# Status: Downloaded newer image for kariricode/php-api-stack:dev -# ✓ Docker image pulled -``` - -### Docker Environment Info -```bash -make docker-info - -# Comprehensive environment details: -# ╔════════════════════════════════════════════════════════╗ -# ║ Docker Environment Information ║ -# ╚════════════════════════════════════════════════════════╝ -# -# Docker Image: kariricode/php-api-stack:dev -# Mount Point: /home/user/project:/app -# -# ╔════════════════════════════════════════════════════════╗ -# ║ Container PHP Info ║ -# ╚════════════════════════════════════════════════════════╝ -# -# PHP 8.4.14 (cli) (built: Jan 15 2025 12:34:56) (NTS) -# Copyright (c) The PHP Group -# Zend Engine v4.4.14 -# -# Composer version 2.8.4 2024-12-12 -``` - -### Clean Docker Resources -```bash -make docker-clean - -# Removes unused Docker resources: -# → Cleaning Docker resources... -# Deleted Containers: -# abc123def456 -# -# Deleted Images: -# untagged: old-image:tag -# -# Total reclaimed space: 1.5GB -# ✓ Docker cleanup complete -``` - ---- - -## Docker Utility Tools - -### Text Processing - -#### JSON Processing (jq) -```bash -# Extract data from JSON files -make docker-jq CMD="'.version' composer.json" - -# Output: -# → Running jq '.version' composer.json in Docker... -# "2.0.0" - -# More examples: -make docker-jq CMD="'.require' composer.json" -make docker-jq CMD="'.authors[0].name' composer.json" -make docker-jq CMD="keys composer.json" -``` - -#### YAML Processing (yq) -```bash -# Parse YAML files -make docker-yq CMD="'.services' docker-compose.yml" - -# Output: -# → Running yq '.services' docker-compose.yml in Docker... -# php: -# image: kariricode/php-api-stack:dev -# ... - -# More examples: -make docker-yq CMD="'.services.php.image' docker-compose.yml" -``` - -### File Viewing & Editing - -#### View Files (less) -```bash -# View file contents -make docker-less CMD="composer.json" - -# Opens less viewer in container -# Press 'q' to quit -``` - -#### Edit Files (vim) -```bash -# Edit files with vim -make docker-vim CMD="src/Parser.php" - -# Opens vim editor in container -# :wq to save and quit -# :q! to quit without saving -``` - -**Note**: File paths relative to `/var/www/html` (container working directory) - -### System Diagnostics - -#### List Open Files (lsof) -```bash -# Check open ports -make docker-lsof CMD="-i" - -# Check specific port -make docker-lsof CMD="-i :8080" - -# Check open files by process -make docker-lsof CMD="-p 1234" -``` - -**Requires**: `--cap-add=SYS_PTRACE` (automatically added by Makefile) - -#### Trace System Calls (strace) -```bash -# Trace PHP execution -make docker-strace CMD="php -v" - -# Output shows system calls: -# execve("/usr/local/bin/php", ["php", "-v"], ...) -# brk(NULL) -# ... - -# Trace script execution -make docker-strace CMD="php script.php" -``` - -**Requires**: `--cap-add=SYS_PTRACE` (automatically added) - -### Network Diagnostics - -#### IP Configuration -```bash -# Show network interfaces -make docker-ip CMD="addr" - -# Output: -# 1: lo: mtu 65536 -# inet 127.0.0.1/8 -# 2: eth0: mtu 1500 -# inet 172.17.0.2/16 - -# Show routing table -make docker-ip CMD="route" -``` - -#### Network Connectivity (netcat) -```bash -# Test port connectivity -make docker-nc CMD="-vz localhost 9000" - -# Output: -# localhost [127.0.0.1] 9000 (?) open - -# Listen on port -make docker-nc CMD="-l 8080" - -# Connect to service -make docker-nc CMD="redis 6379" -``` - ---- - -## Troubleshooting - -### Issue 1: Container Exits Immediately - -**Symptoms:** -```bash -$ make docker-shell -→ Opening Docker shell... -# (exits immediately) -``` - -**Diagnosis:** -```bash -# Check Docker daemon -docker ps - -# Check image integrity -docker images | grep kariricode -``` - -**Solutions:** -```bash -# Pull fresh image -make docker-pull - -# Verify image -docker run --rm kariricode/php-api-stack:dev php -v - -# Check Docker daemon -sudo systemctl status docker -``` - -### Issue 2: Permission Denied Errors - -**Symptoms:** -```bash -$ make docker-composer CMD="install" -Permission denied: composer.lock -``` - -**Cause:** User ID mismatch between host and container - -**Solutions:** - -**Option A: Fix Ownership** -```bash -# On host -sudo chown -R $USER:$USER . - -# Or specific files -sudo chown $USER composer.lock -``` - -**Option B: Run as Non-root** -```bash -# Edit Makefile.docker-core.mk -DOCKER_RUN := docker run --rm \ - -v $(PWD):/var/www/html \ - -w /var/www/html \ - -u $(shell id -u):$(shell id -g) \ # Add this line - kariricode/php-api-stack:dev -``` - -### Issue 3: Xdebug Not Working - -**Symptoms:** -```bash -$ make docker-test -# Tests run but no coverage data -``` - -**Diagnosis:** -```bash -make docker-php CMD="-m | grep xdebug" -# If empty, Xdebug not loaded -``` - -**Solutions:** -```bash -# Check Xdebug configuration -make docker-shell -> php -i | grep xdebug.mode -# Should show: xdebug.mode => coverage - -# If not, configure in docker-compose.yml -# See MAKEFILE-compose.md for Xdebug setup -``` - -### Issue 4: Slow Docker Performance - -**Symptoms:** -- Commands take significantly longer in Docker -- File operations are slow - -**Solutions:** - -**Linux: Use Docker Native** -```bash -# Already optimal on Linux -# No additional configuration needed -``` - -**macOS: Optimize Volume Mounts** -```bash -# Use :delegated flag (already in Makefile) --v $(PWD):/var/www/html:delegated - -# Or use Docker volumes for vendor/ -# docker volume create --name vendor-cache --v vendor-cache:/var/www/html/vendor -``` - -**Windows: Enable WSL2** -```powershell -# Use WSL2 backend (faster than Hyper-V) -wsl --set-default-version 2 -``` - -### Issue 5: Image Pull Fails - -**Symptoms:** -```bash -$ make docker-pull -Error response from daemon: manifest not found -``` - -**Solutions:** -```bash -# Check image exists -docker search kariricode/php-api-stack - -# Try explicit tag -docker pull kariricode/php-api-stack:dev - -# Check Docker Hub status -curl -s https://status.docker.com/api/v2/status.json -``` - ---- - -## Best Practices - -### 1. Pre-commit Workflow -```bash -# Fast local checks -make pre-commit - -# Final validation in Docker (before push) -make docker-ci - -# Catches environment-specific issues -``` - -### 2. CI/CD Strategy -```bash -# GitHub Actions / GitLab CI -# Always use Docker for consistency - -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Run CI in Docker - run: make docker-ci -``` - -### 3. Team Onboarding -```bash -# New developer setup (no local PHP needed) -git clone https://github.com/org/repo.git -cd repo - -# Pull image -make docker-pull - -# Install dependencies in container -make docker-composer CMD="install" - -# Run tests -make docker-test - -# ✅ Ready to develop! -``` - -### 4. Debugging in Docker -```bash -# Interactive debugging session -make docker-shell - -# Inside container: -root@abc:/var/www/html# php -d xdebug.mode=debug \ - -d xdebug.start_with_request=yes \ - vendor/bin/phpunit --filter testMethod - -# Configure IDE to listen on host:9003 -``` - -### 5. Cache Optimization -```bash -# Use Docker volumes for vendor/ -# Speeds up repeated container starts - -# Create volume -docker volume create composer-cache - -# Use in Makefile -DOCKER_RUN := docker run --rm \ - -v $(PWD):/var/www/html \ - -v composer-cache:/var/www/html/vendor \ - kariricode/php-api-stack:dev -``` - -### 6. Resource Limits -```bash -# Prevent runaway containers - -# Limit memory -DOCKER_RUN := docker run --rm \ - -v $(PWD):/var/www/html \ - --memory="1g" \ - --memory-swap="1g" \ - kariricode/php-api-stack:dev - -# Limit CPU -DOCKER_RUN := docker run --rm \ - -v $(PWD):/var/www/html \ - --cpus="2.0" \ - kariricode/php-api-stack:dev -``` - ---- - -## Command Reference - -### Core Commands -```bash -make docker-shell # Interactive bash shell -make docker-composer CMD="..." # Run Composer command -make docker-php CMD="..." # Run PHP command -``` - -### QA Commands -```bash -make docker-test # Run tests -make docker-test-unit # Unit tests -make docker-phpstan # Static analysis -make docker-psalm # Type checking -make docker-analyse # All analysis -make docker-cs-check # Code style check -make docker-format # Auto-fix style -make docker-lint # Syntax check -make docker-coverage # Code coverage -make docker-bench # Benchmarks -``` - -### CI Pipelines -```bash -make docker-ci # Fast CI pipeline -make docker-ci-full # Full CI pipeline -``` - -### Image Management -```bash -make docker-pull # Pull latest image -make docker-info # Show environment info -make docker-clean # Clean unused resources -``` - -### Utility Tools -```bash -make docker-jq CMD="..." # JSON processing -make docker-yq CMD="..." # YAML processing -make docker-less CMD="..." # View files -make docker-vim CMD="..." # Edit files -make docker-lsof CMD="..." # List open files -make docker-strace CMD="..." # Trace system calls -make docker-ip CMD="..." # Network config -make docker-nc CMD="..." # Network connectivity -``` - ---- - -## Quick Reference Card -``` -╔═══════════════════════════════════════════════════════════╗ -║ Docker Commands Quick Reference ║ -╠═══════════════════════════════════════════════════════════╣ -║ SHELL │ make docker-shell ║ -║ COMPOSER │ make docker-composer CMD="install" ║ -║ PHP │ make docker-php CMD="-v" ║ -║──────────────┼────────────────────────────────────────────║ -║ TEST │ make docker-test ║ -║ ANALYSE │ make docker-analyse ║ -║ FORMAT │ make docker-format ║ -║ COVERAGE │ make docker-coverage ║ -║──────────────┼────────────────────────────────────────────║ -║ FAST CI │ make docker-ci ║ -║ FULL CI │ make docker-ci-full ║ -║──────────────┼────────────────────────────────────────────║ -║ PULL IMAGE │ make docker-pull ║ -║ INFO │ make docker-info ║ -║ CLEAN │ make docker-clean ║ -╚═══════════════════════════════════════════════════════════╝ - -When to Use Docker: - ✅ CI/CD pipelines (always) - ✅ Pre-push validation - ✅ Team consistency - ✅ Testing specific PHP versions - -When to Use Local: - ✅ Daily development - ✅ Fast iteration - ✅ IDE integration -``` - ---- - -**Version**: 1.0.0 -**Modules**: `Makefile.docker-*.mk` (except `docker-compose.mk`) - -**Maintainer**: Walmir Silva -**Note**: For Docker Compose full-stack environment management, see [MAKEFILE-compose.md](MAKEFILE-compose.md) -``` diff --git a/.docs/MAKEFILE-helpers.md b/.docs/MAKEFILE-helpers.md deleted file mode 100644 index 8458bb5..0000000 --- a/.docs/MAKEFILE-helpers.md +++ /dev/null @@ -1,1016 +0,0 @@ -
- -# Development Helpers - -[![KaririCode](https://img.shields.io/badge/KaririCode-DevKit-6D00CC?style=for-the-badge)](https://github.com/KaririCode-Framework/kariricode-devkit) -[![Benchmarks](https://img.shields.io/badge/PHPBench-Performance-FF6F00?style=for-the-badge)](https://github.com/phpbench/phpbench) -[![Git](https://img.shields.io/badge/Git-Hooks-F05032?style=for-the-badge&logo=git)](https://git-scm.com) - -**kariricode/devkit** - Professional development environment for KaririCode Framework - -Part of the [KaririCode Framework](https://kariricode.org) ecosystem - -[Main Documentation](MAKEFILE.md) | [GitHub Repository](https://github.com/KaririCode-Framework/kariricode-devkit) - -
- ---- - -## Table of Contents - -1. [Overview](#overview) -2. [Benchmarking](#benchmarking) -3. [Git Hooks Management](#git-hooks-management) -4. [Release Management](#release-management) -5. [Project Information](#project-information) -6. [Statistics & Metrics](#statistics--metrics) -7. [Best Practices](#best-practices) - ---- - -## Overview - -### Scope - -The helpers module (`Makefile.helpers.mk`) provides developer productivity tools: -- **Benchmarking**: Performance measurement with PHPBench -- **Git Hooks**: Automated pre-commit quality checks -- **Release Management**: Version tagging and release preparation -- **Project Info**: Environment and tool verification -- **Statistics**: Code metrics and project analytics - -### Module Architecture -``` -Helpers Module (.make/local/Makefile.helpers.mk) -│ -├── Benchmarking -│ ├── bench → Unified benchmark command -│ └── bench-help → Parameter documentation -│ -├── Git Hooks -│ ├── git-hooks-setup → Install pre-commit hook -│ ├── git-hooks-check → Verify installation -│ └── git-hooks-remove → Cleanup hooks -│ -├── Release Management -│ ├── tag → Create version tag -│ └── release → Release preparation -│ -├── Information -│ └── info → Environment details -│ -└── Statistics - ├── stats → Project statistics - └── loc → Lines of code count -``` - ---- - -## Benchmarking - -### Unified Benchmark Interface - -#### Overview - -The `make bench` command provides a **single, parameter-driven interface** for all benchmarking scenarios: -- Standard benchmarks -- Performance comparisons -- Result storage with tagging -- Automated baseline management - -#### Basic Usage -```bash -# Simple benchmark run -make bench - -# If no 'main' baseline exists: -# → Running benchmarks (unified target)… -# ⚠ No 'main' reference found. Running without comparison. -# Hint: make bench STORE=1 TAG=main ENFORCE_MAIN=1 -# ✓ Benchmarks complete -``` - -### Parameters - -| Parameter | Values | Default | Description | -|-----------|--------|---------|-------------| -| **REF** | `auto`, `main`, `` | `auto` | Compare against stored reference | -| **STORE** | `0`, `1` | `0` | Store results with tag | -| **TAG** | `` | - | Tag name when storing (required if STORE=1) | -| **ENFORCE_MAIN** | `0`, `1` | `0` | Require 'main' branch when TAG=main | -| **REPORT** | `0`, `1` | `0` | Save output to `build/benchmarks/last.txt` | - -### Reference Comparison (REF) - -#### Auto-detect Reference -```bash -# Try 'main' baseline if available -make bench REF=auto - -# If 'main' exists: -# ✓ 'main' reference found. Enabling comparison… -# Shows comparison against main baseline - -# If 'main' doesn't exist: -# ⚠ No 'main' reference found. Running without comparison. -``` - -#### Compare Against Main -```bash -make bench REF=main - -# Output: -# → Comparing against reference: main -# -# Subject Groups Iters Mean Diff (%) -# benchTokenize 10 127.234μs -12.5% ← Faster -# benchParse 10 456.789μs +5.2% ← Slower -``` - -#### Compare Against Custom Tag -```bash -make bench REF=feature-x - -# Compares current code against stored 'feature-x' baseline -``` - -### Storing Results (STORE + TAG) - -#### Store Feature Baseline -```bash -make bench STORE=1 TAG=feature-optimization - -# → Storing run with tag 'feature-optimization'… -# Stores current performance metrics -# ✓ Benchmarks complete -``` - -#### Store Main Baseline (with branch enforcement) -```bash -# Must be on 'main' branch -git checkout main -make bench STORE=1 TAG=main ENFORCE_MAIN=1 - -# If not on main: -# ✗ This action requires branch 'main' (current: develop) - -# If on main: -# → Storing run with tag 'main'… -# ✓ Benchmarks complete -``` - -### Save Output to File (REPORT) -```bash -# Compare and save detailed report -make bench REF=main REPORT=1 - -# Output saved to: build/benchmarks/last.txt -# ✓ Output saved to build/benchmarks/last.txt - -# View report -cat build/benchmarks/last.txt -``` - -### Complete Workflow Examples - -#### Baseline Creation Workflow -```bash -# 1. Create initial baseline on main branch -git checkout main -git pull origin main - -# 2. Store as 'main' reference (with enforcement) -make bench STORE=1 TAG=main ENFORCE_MAIN=1 - -# Output: -# → Storing run with tag 'main'… -# -# Subject Groups Iters Mean -# benchTokenize 10 145.234μs -# benchParse 10 432.156μs -# -# ✓ Benchmarks complete -``` - -#### Optimization Workflow -```bash -# 1. Create optimization branch -git checkout -b optimize/lexer-performance - -# 2. Make optimization changes -# (edit source files) - -# 3. Run benchmark with comparison -make bench REF=main - -# Output shows performance delta: -# benchTokenize: 127.234μs (-12.4%) ← 12.4% faster! ✓ - -# 4. Store optimization attempt -make bench STORE=1 TAG=opt-attempt-1 - -# 5. Make more changes -# (edit source files) - -# 6. Compare against previous attempt -make bench REF=opt-attempt-1 - -# 7. Save final report -make bench REF=main REPORT=1 - -# 8. Review detailed comparison -cat build/benchmarks/last.txt -``` - -#### Feature Development Workflow -```bash -# 1. Create feature branch -git checkout -b feature/new-parser - -# 2. Develop feature with periodic benchmarks -make bench REF=main - -# 3. Store milestone benchmarks -make bench STORE=1 TAG=feature-milestone-1 -# (develop more) -make bench STORE=1 TAG=feature-milestone-2 - -# 4. Compare milestones -make bench REF=feature-milestone-1 - -# 5. Before merging to main -make bench REF=main REPORT=1 -# Ensure no performance regression -``` - -#### Release Workflow -```bash -# Before release, verify performance -git checkout main -make bench REF=main REPORT=1 - -# Store release baseline -make bench STORE=1 TAG=v2.0.0 - -# Archive report -cp build/benchmarks/last.txt docs/performance/v2.0.0-benchmark.txt -git add docs/performance/v2.0.0-benchmark.txt -git commit -m "docs: add v2.0.0 performance baseline" -``` - -### Benchmark Help -```bash -make bench-help - -# Shows comprehensive parameter documentation: -# -# ╔════════════════════════════════════════════════════════╗ -# ║ Benchmark Command Help ║ -# ╚════════════════════════════════════════════════════════╝ -# -# Single entrypoint: make bench -# -# Parameters: -# REF=auto|main| Compare against a stored tag -# STORE=1 TAG= Store benchmarks with a tag -# ENFORCE_MAIN=1 Require 'main' branch for TAG=main -# REPORT=1 Save output to last.txt -# -# Examples: -# make bench # Normal run -# make bench REF=main # Compare vs main -# make bench STORE=1 TAG=feat # Store as 'feat' -# make bench REF=main REPORT=1 # Compare and save -``` - -### Benchmark Configuration - -#### PHPBench Configuration (phpbench.json) -```json -{ - "runner.bootstrap": "vendor/autoload.php", - "runner.path": "benchmarks", - "runner.progress": "dots", - "runner.iterations": [10], - "runner.revs": [100], - "runner.warmup": [1], - "report.generators": { - "default": { - "extends": "aggregate" - } - } -} -``` - -#### Benchmark Example -```php -// benchmarks/LexerBench.php -lexer = new Lexer(); - $this->sourceCode = file_get_contents(__DIR__ . '/fixtures/sample.php'); - } - - /** - * @Subject - */ - public function benchTokenize(): void - { - $this->lexer->tokenize($this->sourceCode); - } - - /** - * @Subject - * @ParamProviders({"provideCodeSamples"}) - */ - public function benchTokenizeVariousSizes(array $params): void - { - $this->lexer->tokenize($params['code']); - } - - public function provideCodeSamples(): \Generator - { - yield 'small' => ['code' => ' ['code' => file_get_contents(__DIR__ . '/fixtures/medium.php')]; - yield 'large' => ['code' => file_get_contents(__DIR__ . '/fixtures/large.php')]; - } -} -``` - -### Performance Analysis Tips - -#### Interpret Results -``` -Subject Groups Iters Mean Stddev RPS -benchTokenize lexer 10 127.234μs ±2.34% 7,859.2 - -Mean: Average execution time -Stddev: Standard deviation (consistency indicator) -RPS: Requests per second (throughput) -``` - -**Good Performance:** -- Low mean time -- Low standard deviation (< 5%) -- High RPS - -#### Optimization Checklist -```bash -# 1. Create baseline -make bench STORE=1 TAG=baseline - -# 2. Profile hot paths -make bench REF=baseline -# Identify slowest operations - -# 3. Optimize critical sections -# (implement optimizations) - -# 4. Verify improvement -make bench REF=baseline -# Must show negative % (faster) - -# 5. Check for regressions -make bench REF=main -# Ensure no other operations slowed down -``` - ---- - -## Git Hooks Management - -### Pre-commit Hook Installation - -#### Setup Hook -```bash -make git-hooks-setup - -# What it does: -# 1. Creates .git/hooks/pre-commit -# 2. Backs up existing hook (if present) → .git/hooks/pre-commit.bak -# 3. Writes 'make pre-commit' script -# 4. Makes hook executable (chmod +x) - -# Output: -# → Setting up git hooks... -# ✓ Git hooks set up -``` - -#### Hook Script Content -```bash -# .git/hooks/pre-commit -#!/bin/sh -set -e -make pre-commit -``` - -**Behavior:** -- Runs automatically before each commit -- Executes: `format → lint → analyse → test-unit` -- Blocks commit if any check fails -- Exit code determines commit success - -### Verify Installation -```bash -make git-hooks-check - -# Checks: -# 1. Hook file exists (.git/hooks/pre-commit) -# 2. Contains 'make pre-commit' command -# 3. Is executable - -# Output (if installed): -# → Verifying git hooks... -# ✓ pre-commit hook is installed correctly - -# Output (if not installed): -# → Verifying git hooks... -# ✗ pre-commit hook not found -``` - -### Remove Hook -```bash -make git-hooks-remove - -# What it does: -# 1. Checks for backup (.git/hooks/pre-commit.bak) -# 2. If backup exists: Restores it -# 3. If no backup: Removes generated hook -# 4. Cleanup - -# Output (with backup): -# → Cleaning up git hooks... -# ↩ Restoring backup pre-commit hook... -# ✓ Git hooks cleaned - -# Output (without backup): -# → Cleaning up git hooks... -# ✗ Removing generated pre-commit hook... -# ✓ Git hooks cleaned -``` - -### Hook Workflow - -#### Commit with Hook Active -```bash -$ git add src/Parser.php -$ git commit -m "feat: optimize parser performance" - -# Hook executes automatically: -→ Running pre-commit checks... - -→ Formatting code... -✓ Code formatted - -→ Linting PHP files... -✓ All PHP files are valid - -→ Running static analysis... -✓ PHPStan analysis passed -✓ Psalm analysis passed - -→ Running unit tests... -OK (95 tests, 280 assertions) -✓ Unit tests passed - -✓ Pre-commit checks passed - -[feature/optimize abc123] feat: optimize parser performance - 1 file changed, 10 insertions(+), 5 deletions(-) -``` - -#### Commit Failed by Hook -```bash -$ git commit -m "wip: broken code" - -→ Running pre-commit checks... - -→ Formatting code... -✓ Code formatted - -→ Linting PHP files... -Parse error: syntax error, unexpected 'class' -✗ Linting failed - -# Commit blocked - fix issues first -``` - -#### Bypass Hook (Emergency) -```bash -# Override hook (use sparingly!) -git commit --no-verify -m "emergency: critical hotfix" - -# ⚠️ Still run checks after: -make pre-commit -``` - -### Custom Hook Configuration - -#### Conditional Execution -```bash -# Edit: .git/hooks/pre-commit -#!/bin/sh -set -e - -# Get staged files -STAGED=$(git diff --cached --name-only --diff-filter=ACM | grep '\.php$' || true) - -# Only run checks if PHP files changed -if [ -n "$STAGED" ]; then - echo "PHP files changed, running checks..." - make pre-commit -else - echo "No PHP files changed, skipping checks" -fi -``` - -#### Partial Checks -```bash -# Edit: .git/hooks/pre-commit -#!/bin/sh -set -e - -# Check if only documentation changed -CHANGED=$(git diff --cached --name-only) - -if echo "$CHANGED" | grep -qv "\.md$"; then - # Source code changed: full checks - make pre-commit -else - # Only docs changed: format and lint only - make format - make lint -fi -``` - ---- - -## Release Management - -### Create Version Tag - -#### Basic Usage -```bash -make tag VERSION=1.0.0 - -# Executes: -# 1. Validates VERSION parameter -# 2. Creates annotated git tag: v1.0.0 -# 3. Pushes tag to origin - -# Output: -# → Creating tag v1.0.0... -# ✓ Tag v1.0.0 created and pushed -``` - -#### Tag Format -```bash -# Semantic Versioning (MAJOR.MINOR.PATCH) -make tag VERSION=1.0.0 # v1.0.0 -make tag VERSION=2.1.0 # v2.1.0 -make tag VERSION=2.1.5 # v2.1.5 - -# Pre-release tags -make tag VERSION=1.0.0-alpha.1 -make tag VERSION=2.0.0-beta.2 -make tag VERSION=1.0.0-rc.1 -``` - -#### Tag Annotation -```makefile -# Makefile creates annotated tag with message -git tag -a "v$(VERSION)" -m "Release v$(VERSION)" - -# View tag details: -git show v1.0.0 -``` - -### Release Preparation -```bash -make release - -# Executes complete release workflow: -# 1. make cd → Full validation pipeline -# 2. Display checklist → Manual steps reminder - -# Output: -# ╔════════════════════════════════════════════════════════╗ -# ║ KaririCode\DevKit CD Pipeline ║ -# ╚════════════════════════════════════════════════════════╝ -# -# [Runs ci-full + benchmarks...] -# -# ✓ Release preparation complete -# -# Next steps: -# 1. Update CHANGELOG.md -# 2. Update version in composer.json -# 3. Commit changes -# 4. Run: make tag VERSION=X.Y.Z -# 5. Push to GitHub -# 6. Create GitHub release -``` - -### Complete Release Workflow - -#### Step-by-Step Release -```bash -# 1. Ensure clean state -git checkout main -git pull origin main -git status # Should be clean - -# 2. Run release preparation -make release - -# 3. Update CHANGELOG.md -nano CHANGELOG.md -# Add release notes: -# ## [2.0.0] - 2025-01-15 -# ### Added -# - New feature X -# ### Changed -# - Improved Y -# ### Fixed -# - Bug Z - -# 4. Update version in composer.json -nano composer.json -# Change: "version": "2.0.0" - -# 5. Commit changes -git add CHANGELOG.md composer.json -git commit -m "chore: release v2.0.0" - -# 6. Create and push tag -make tag VERSION=2.0.0 - -# 7. Push commits -git push origin main - -# 8. Create GitHub release (manual or gh CLI) -gh release create v2.0.0 \ - --title "Release v2.0.0" \ - --notes-file CHANGELOG.md \ - --generate-notes -``` - -#### Automated Release Script -```bash -#!/bin/bash -# scripts/release.sh - -set -e - -VERSION=$1 - -if [ -z "$VERSION" ]; then - echo "Usage: ./scripts/release.sh VERSION" - exit 1 -fi - -echo "Preparing release $VERSION..." - -# 1. Run release checks -make release - -# 2. Update version in composer.json -sed -i "s/\"version\": \".*\"/\"version\": \"$VERSION\"/" composer.json - -# 3. Update CHANGELOG -DATE=$(date +%Y-%m-%d) -sed -i "s/## \[Unreleased\]/## [$VERSION] - $DATE/" CHANGELOG.md - -# 4. Commit -git add composer.json CHANGELOG.md -git commit -m "chore: release v$VERSION" - -# 5. Tag -make tag VERSION=$VERSION - -# 6. Push -git push origin main - -echo "✓ Release $VERSION complete!" -``` - ---- - -## Project Information - -### Environment Details -```bash -make info - -# Shows comprehensive project information: -# -# ╔════════════════════════════════════════════════════════╗ -# ║ Project Information ║ -# ╚════════════════════════════════════════════════════════╝ -# -# PHP Version: 8.4.14 -# PHP Binary: /usr/bin/php -# Composer: /usr/local/bin/composer -# Project Directory: /home/user/kariricode-devkit -# Source Directory: src -# Test Directory: tests -# -# ╔════════════════════════════════════════════════════════╗ -# ║ Installed Tools ║ -# ╚════════════════════════════════════════════════════════╝ -# -# PHPUnit: ✓ -# PHPStan: ✓ -# Psalm: ✓ -# PHPCS: ✓ -# PHP-CS-Fixer: ✓ -# Infection: ✓ -# PHPBench: ✓ -``` - -### Use Cases - -**New Developer Onboarding:** -```bash -# Clone repository -git clone https://github.com/KaririCode-Framework/kariricode-devkit.git -cd kariricode-devkit - -# Verify environment -make info - -# If tools missing (✗): -make install-dev -make info # Should all show ✓ -``` - -**Troubleshooting:** -```bash -# Check environment before reporting issues -make info > environment.txt - -# Include in bug report -``` - -**Documentation:** -```bash -# Generate environment report for README -make info -# Copy output to documentation -``` - ---- - -## Statistics & Metrics - -### Project Statistics -```bash -make stats - -# Comprehensive project metrics: -# -# ╔════════════════════════════════════════════════════════╗ -# ║ Project Statistics ║ -# ╚════════════════════════════════════════════════════════╝ -# -# Total PHP files: 150 -# Total test files: 95 -# Lines of code: 12,456 -# Lines of tests: 8,932 -# -# ╔════════════════════════════════════════════════════════╗ -# ║ Directory Sizes ║ -# ╚════════════════════════════════════════════════════════╝ -# -# src 2.3M -# tests 1.8M -# vendor 45M -``` - -### Lines of Code -```bash -make loc - -# Quick line count: -# -# → Counting lines of code... -# Source: 12456 lines -# Tests: 8932 lines -``` - -### Metrics Over Time -```bash -# Track code growth weekly -{ - echo "$(date): $(make loc 2>&1 | grep Source)" -} >> metrics/weekly-loc.txt - -# Visualize growth -cat metrics/weekly-loc.txt -# 2025-01-08: Source: 11234 lines -# 2025-01-15: Source: 12456 lines -# Growth: +1222 lines (+10.9%) -``` - -### Test Coverage Ratio -```bash -# Calculate test-to-code ratio -SRC_LINES=$(find src -name '*.php' -exec wc -l {} \; | awk '{sum += $1} END {print sum}') -TEST_LINES=$(find tests -name '*.php' -exec wc -l {} \; | awk '{sum += $1} END {print sum}') - -RATIO=$(echo "scale=2; $TEST_LINES / $SRC_LINES" | bc) -echo "Test-to-Code Ratio: $RATIO" - -# Good ratios: -# 0.5-0.8: Healthy (50-80% test code relative to source) -# 0.8-1.2: Excellent (comprehensive testing) -# > 1.2: Possibly over-testing -``` - ---- - -## Best Practices - -### 1. Benchmark Regularly -```bash -# Weekly performance checks -make bench REF=main REPORT=1 - -# Before optimization -make bench STORE=1 TAG=pre-optimization - -# After optimization -make bench REF=pre-optimization REPORT=1 -``` - -### 2. Enforce Pre-commit Hooks -```bash -# Team setup script -# setup-team.sh -#!/bin/bash - -echo "Setting up development environment..." -make install-dev -make git-hooks-setup -make info - -echo "✓ Setup complete. Pre-commit hooks active." -``` - -### 3. Document Releases Thoroughly -```markdown - -## [2.0.0] - 2025-01-15 - -### Added -- New parser architecture with AST nodes -- Benchmark suite for performance tracking - -### Changed -- **BREAKING**: Renamed `Lexer::parse()` to `Lexer::tokenize()` -- Improved error messages with context - -### Fixed -- Memory leak in recursive parsing -- Thread safety in token cache - -### Performance -- 25% faster tokenization (see benchmarks/v2.0.0.txt) -- 40% reduction in memory usage - -### Security -- Fixed XSS vulnerability in error output (CVE-2025-XXXX) -``` - -### 4. Automate Release Process -```bash -# .github/workflows/release.yml -name: Release - -on: - workflow_dispatch: - inputs: - version: - description: 'Version to release (e.g., 1.0.0)' - required: true - -jobs: - release: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.4' - - - name: Run release checks - run: make release - - - name: Update version - run: | - sed -i "s/\"version\": \".*\"/\"version\": \"${{ github.event.inputs.version }}\"/" composer.json - git config user.name "GitHub Actions" - git config user.email "actions@github.com" - git add composer.json - git commit -m "chore: release v${{ github.event.inputs.version }}" - - - name: Create tag - run: make tag VERSION=${{ github.event.inputs.version }} -``` - ---- - -## Command Reference - -### Benchmarking -```bash -make bench # Run benchmarks -make bench REF=main # Compare against main -make bench STORE=1 TAG=feature # Store with tag -make bench REF=main REPORT=1 # Compare and save report -make bench-help # Show parameter help -``` - -### Git Hooks -```bash -make git-hooks-setup # Install pre-commit hook -make git-hooks-check # Verify installation -make git-hooks-remove # Remove hook -``` - -### Release Management -```bash -make tag VERSION=X.Y.Z # Create version tag -make release # Release preparation -``` - -### Information & Stats -```bash -make info # Environment information -make stats # Project statistics -make loc # Lines of code count -``` - ---- - -## Quick Reference Card -``` -╔═══════════════════════════════════════════════════════════╗ -║ Development Helpers Quick Reference ║ -╠═══════════════════════════════════════════════════════════╣ -║ BENCHMARK │ make bench ║ -║ COMPARE │ make bench REF=main ║ -║ STORE │ make bench STORE=1 TAG=name ║ -║ REPORT │ make bench REF=main REPORT=1 ║ -║──────────────┼────────────────────────────────────────────║ -║ SETUP HOOKS │ make git-hooks-setup ║ -║ CHECK HOOKS │ make git-hooks-check ║ -║ REMOVE HOOKS │ make git-hooks-remove ║ -║──────────────┼────────────────────────────────────────────║ -║ CREATE TAG │ make tag VERSION=1.0.0 ║ -║ RELEASE │ make release ║ -║──────────────┼────────────────────────────────────────────║ -║ INFO │ make info ║ -║ STATS │ make stats ║ -║ LOC │ make loc ║ -╚═══════════════════════════════════════════════════════════╝ - -Benchmark Workflow: - 1. make bench STORE=1 TAG=main ENFORCE_MAIN=1 - 2. (make changes) - 3. make bench REF=main - 4. make bench STORE=1 TAG=feature-name - -Release Workflow: - 1. make release - 2. Update CHANGELOG.md + composer.json - 3. git commit -m "chore: release vX.Y.Z" - 4. make tag VERSION=X.Y.Z -``` - ---- - -**Version**: 1.0.0 -**Module**: `Makefile.helpers.mk` -**Maintainer**: Walmir Silva diff --git a/.docs/MAKEFILE-pipeline.md b/.docs/MAKEFILE-pipeline.md deleted file mode 100644 index e44f500..0000000 --- a/.docs/MAKEFILE-pipeline.md +++ /dev/null @@ -1,949 +0,0 @@ -
- -# CI/CD Pipeline Orchestration - -[![KaririCode](https://img.shields.io/badge/KaririCode-DevKit-6D00CC?style=for-the-badge)](https://github.com/KaririCode-Framework/kariricode-devkit) -[![CI](https://img.shields.io/badge/CI-Automated-2088FF?style=for-the-badge&logo=github-actions)](https://github.com/features/actions) -[![CD](https://img.shields.io/badge/CD-Release%20Ready-00C853?style=for-the-badge&logo=gitbook)](https://github.com/features/actions) - -**kariricode/devkit** - Professional development environment for KaririCode Framework - -Part of the [KaririCode Framework](https://kariricode.org) ecosystem - -[Main Documentation](MAKEFILE.md) | [GitHub Repository](https://github.com/KaririCode-Framework/kariricode-devkit) - -
- ---- - -## Table of Contents - -1. [Overview](#overview) -2. [Pipeline Architecture](#pipeline-architecture) -3. [CI Pipelines](#ci-pipelines) -4. [CD Pipeline](#cd-pipeline) -5. [Pre-commit Hooks](#pre-commit-hooks) -6. [CI/CD Integration](#cicd-integration) -7. [Pipeline Optimization](#pipeline-optimization) -8. [Best Practices](#best-practices) - ---- - -## Overview - -### Scope - -The pipeline module (`Makefile.orchestration.mk`) provides: -- **CI Pipeline**: Fast continuous integration checks -- **Full CI Pipeline**: Comprehensive quality validation -- **CD Pipeline**: Release preparation workflow -- **Pre-commit Pipeline**: Developer quality gates - -### Design Philosophy -``` -┌─────────────────────────────────────────────────────────┐ -│ Pipeline Orchestration Philosophy │ -├─────────────────────────────────────────────────────────┤ -│ │ -│ COMPOSABILITY → Build complex from simple │ -│ FAIL-FAST → Stop on first error │ -│ FEEDBACK SPEED → Critical checks first │ -│ IDEMPOTENCY → Same result every run │ -│ ISOLATION → No side effects between stages │ -│ │ -└─────────────────────────────────────────────────────────┘ -``` - -### Pipeline Composition -```makefile -# Simple targets (atomic operations) -lint: @vendor/bin/phplint -cs-check: @vendor/bin/phpcs -phpstan: @vendor/bin/phpstan -test: @vendor/bin/phpunit - -# Composed pipelines (orchestrated workflows) -ci: lint + cs-check + phpstan + psalm + test -ci-full: ci + coverage + mutation + security -cd: ci-full + bench + release-prep -``` - ---- - -## Pipeline Architecture - -### Execution Flow -``` -┌─────────────────────────────────────────────────────────┐ -│ Pipeline Stages │ -├─────────────────────────────────────────────────────────┤ -│ │ -│ Stage 1: Prerequisites │ -│ ├─ check-php (PHP version validation) │ -│ └─ verify-install (Dependencies check) │ -│ ↓ │ -│ Stage 2: Fast Checks (< 30 seconds) │ -│ ├─ lint (Syntax errors) │ -│ ├─ cs-check (Code style) │ -│ └─ validate (composer.json) │ -│ ↓ │ -│ Stage 3: Static Analysis (1-2 minutes) │ -│ ├─ phpstan (Type errors) │ -│ └─ psalm (Logic errors) │ -│ ↓ │ -│ Stage 4: Testing (1-3 minutes) │ -│ └─ test (Unit + Integration) │ -│ ↓ │ -│ Stage 5: Quality Metrics (3-5 minutes) [OPTIONAL] │ -│ ├─ coverage (Code coverage) │ -│ └─ mutation (Mutation testing) │ -│ ↓ │ -│ Stage 6: Security (< 30 seconds) │ -│ └─ security (Vulnerability scan) │ -│ ↓ │ -│ Stage 7: Performance (2-5 minutes) [CD ONLY] │ -│ └─ bench (Benchmarking) │ -│ │ -└─────────────────────────────────────────────────────────┘ -``` - -### Fail-Fast Strategy -```makefile -# Each stage must pass before proceeding -ci: - @$(MAKE) --no-print-directory check-php # FAIL → STOP - @$(MAKE) --no-print-directory lint # FAIL → STOP - @$(MAKE) --no-print-directory cs-check # FAIL → STOP - @$(MAKE) --no-print-directory phpstan # FAIL → STOP - @$(MAKE) --no-print-directory psalm # FAIL → STOP - @$(MAKE) --no-print-directory test # FAIL → STOP - @echo "✓ CI pipeline completed" # ALL PASSED -``` - ---- - -## CI Pipelines - -### Fast CI Pipeline - -#### Purpose - -**Optimize for developer feedback speed:** -- ✅ Essential quality checks only -- ✅ Target execution: < 2 minutes -- ✅ Run on every commit -- ✅ High signal-to-noise ratio - -#### Command -```bash -make ci - -# Executes in sequence: -# 1. check-php → PHP 8.4+ validation -# 2. lint → Syntax errors -# 3. cs-check → Code style violations -# 4. phpstan → Static analysis (level max) -# 5. psalm → Type checking -# 6. test → Test suite execution - -# Total time: ~1-2 minutes -``` - -#### Output Example -```bash -$ make ci - -╔════════════════════════════════════════════════════════╗ -║ KaririCode\DevKit CI Pipeline ║ -╚════════════════════════════════════════════════════════╝ - -→ Checking PHP version... -✓ PHP version 8.4.14 OK (>= 8.4.0) - -→ Linting PHP files... -✓ All PHP files are valid - -→ Checking coding standards... -✓ Coding standards check passed - -→ Running PHPStan... -✓ PHPStan analysis passed - -→ Running Psalm... -✓ Psalm analysis passed - -→ Running tests... -OK (150 tests, 420 assertions) -✓ Tests passed - -✓ CI pipeline completed successfully - -Time: 1m 23s -``` - -#### When to Use - -| Scenario | Use CI | -|----------|--------| -| Local development | ✅ Before every commit | -| Pull request checks | ✅ Required status check | -| Branch protection | ✅ Merge requirement | -| Pre-push hook | ✅ Optional automation | - -### Full CI Pipeline - -#### Purpose - -**Comprehensive quality validation:** -- ✅ All CI checks + extended quality metrics -- ✅ Target execution: 3-5 minutes -- ✅ Run before release -- ✅ Production readiness verification - -#### Command -```bash -make ci-full - -# Executes ALL stages: -# 1. check-php → PHP version -# 2. validate → composer.json validation -# 3. security → Vulnerability scan -# 4. lint → Syntax check -# 5. cs-check → Code style -# 6. phpstan → Static analysis -# 7. psalm → Type checking -# 8. test → All tests -# 9. coverage → Code coverage (requires Xdebug) -# 10. mutation → Mutation testing (slow) - -# Total time: ~3-5 minutes -``` - -#### Output Example -```bash -$ make ci-full - -╔════════════════════════════════════════════════════════╗ -║ KaririCode\DevKit Full CI Pipeline ║ -╚════════════════════════════════════════════════════════╝ - -→ Checking PHP version... -✓ PHP version 8.4.14 OK (>= 8.4.0) - -→ Validating composer.json... -✓ composer.json is valid - -→ Checking for security vulnerabilities... -✓ No security vulnerabilities found - -→ Linting PHP files... -✓ All PHP files are valid - -→ Checking coding standards... -✓ Coding standards check passed - -→ Running PHPStan... -✓ PHPStan analysis passed - -→ Running Psalm... -✓ Psalm analysis passed - -→ Running tests... -OK (150 tests, 420 assertions) -✓ Tests passed - -→ Generating code coverage report... -Code Coverage: 79.24% (1234/1557 lines) -✓ Coverage report generated: coverage/html/index.html - -→ Running mutation tests... -Mutation Score Indicator (MSI): 82% -Covered Code MSI: 92% -✓ Mutation testing complete - -✓ Full CI pipeline completed successfully - -Time: 4m 37s -``` - -#### When to Use - -| Scenario | Use CI-Full | -|----------|-------------| -| Release candidates | ✅ Required | -| Main branch merges | ✅ Recommended | -| Weekly quality check | ✅ Best practice | -| Production deployment | ✅ Mandatory | - -### Pipeline Comparison - -| Feature | CI (Fast) | CI-Full | -|---------|-----------|---------| -| **Execution Time** | ~1-2 min | ~3-5 min | -| **PHP Version Check** | ✅ | ✅ | -| **Syntax Linting** | ✅ | ✅ | -| **Code Style** | ✅ | ✅ | -| **Static Analysis** | ✅ | ✅ | -| **Tests** | ✅ | ✅ | -| **Composer Validation** | ❌ | ✅ | -| **Security Scan** | ❌ | ✅ | -| **Code Coverage** | ❌ | ✅ | -| **Mutation Testing** | ❌ | ✅ | -| **Use Case** | Every commit | Pre-release | - ---- - -## CD Pipeline - -### Purpose - -**Complete release readiness validation:** -- ✅ Full CI-Full pipeline -- ✅ Performance benchmarking -- ✅ Release preparation checklist -- ✅ Final production verification - -### Command -```bash -make cd - -# Executes: -# 1. ci-full → Complete quality validation -# 2. bench → Performance baseline -# 3. Release checklist display - -# Total time: ~5-8 minutes -``` - -### Output Example -```bash -$ make cd - -╔════════════════════════════════════════════════════════╗ -║ KaririCode\DevKit CD Pipeline ║ -╚════════════════════════════════════════════════════════╝ - -[Running ci-full pipeline...] -✓ Full CI pipeline completed successfully - -→ Running benchmarks... -✓ Benchmarks complete - -✓ CD pipeline completed - Ready for release - -Next steps: - 1. Update CHANGELOG.md - 2. Update version in composer.json - 3. Commit changes - 4. Run: make tag VERSION=X.Y.Z - 5. Push to GitHub - 6. Create GitHub release - -Time: 6m 12s -``` - -### Release Workflow Integration -```bash -# Complete release workflow - -# 1. Ensure clean state -git checkout main -git pull origin main - -# 2. Run CD pipeline -make cd - -# 3. Update version files -nano CHANGELOG.md -nano composer.json - -# 4. Commit changes -git add CHANGELOG.md composer.json -git commit -m "chore: release v2.0.0" - -# 5. Create and push tag -make tag VERSION=2.0.0 - -# 6. Push changes -git push origin main --tags - -# 7. Create GitHub release (manual or automated) -``` - ---- - -## Pre-commit Hooks - -### Purpose - -**Developer quality gate before commit:** -- ✅ Fast feedback (30-60 seconds) -- ✅ Auto-fix common issues -- ✅ Essential checks only -- ✅ Prevent broken commits - -### Command -```bash -make pre-commit - -# Executes: -# 1. format → Auto-fix code style -# 2. lint → Syntax validation -# 3. analyse → Static analysis (phpstan + psalm) -# 4. test-unit → Unit tests only (fast) - -# Total time: ~30-60 seconds -``` - -### Output Example -```bash -$ make pre-commit - -→ Running pre-commit checks... - -→ Formatting code... -Fixed 3 files in 1.234 seconds -✓ Code formatted - -→ Linting PHP files... -✓ All PHP files are valid - -→ Running static analysis... -✓ PHPStan analysis passed -✓ Psalm analysis passed - -→ Running unit tests... -OK (95 tests, 280 assertions) -✓ Unit tests passed - -✓ Pre-commit checks passed - -Time: 0m 42s -``` - -### Git Hook Installation - -#### Setup Hook -```bash -# Install pre-commit hook -make git-hooks-setup - -# What it does: -# 1. Creates .git/hooks/pre-commit -# 2. Backs up existing hook (if any) -# 3. Makes hook executable -# 4. Configures to run 'make pre-commit' - -# Output: -# → Setting up git hooks... -# ✓ Git hooks set up -``` - -#### Verify Installation -```bash -make git-hooks-check - -# Output: -# → Verifying git hooks... -# ✓ pre-commit hook is installed correctly -``` - -#### Remove Hook -```bash -make git-hooks-remove - -# What it does: -# 1. Removes .git/hooks/pre-commit -# 2. Restores backup (if exists) - -# Output: -# → Cleaning up git hooks... -# ↩ Restoring backup pre-commit hook... -# ✓ Git hooks cleaned -``` - -### Hook Bypass (Emergency) -```bash -# Bypass hook for emergency commits -git commit --no-verify -m "emergency: fix critical bug" - -# ⚠️ Use sparingly - still run checks after: -make pre-commit -``` - -### Custom Pre-commit Configuration -```bash -# Edit .git/hooks/pre-commit after installation -nano .git/hooks/pre-commit - -# Example: Skip tests if changing docs only -#!/bin/sh -set -e - -# Get changed files -CHANGED=$(git diff --cached --name-only) - -# Skip tests if only docs changed -if echo "$CHANGED" | grep -qv "\.md$"; then - make pre-commit -else - echo "Only docs changed, skipping full checks" - make format - make lint -fi -``` - ---- - -## CI/CD Integration - -### GitHub Actions - -#### Basic CI Workflow -```yaml -# .github/workflows/ci.yml -name: CI - -on: - push: - branches: [ main, develop ] - pull_request: - branches: [ main ] - -jobs: - ci: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.4' - extensions: mbstring, xml, pcov - coverage: pcov - - - name: Cache Composer dependencies - uses: actions/cache@v3 - with: - path: vendor - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- - - - name: Install dependencies - run: make install - - - name: Run CI pipeline - run: make ci - - - name: Upload test results - if: always() - uses: actions/upload-artifact@v3 - with: - name: test-results - path: build/reports/ -``` - -#### Full CI Workflow (with coverage) -```yaml -# .github/workflows/ci-full.yml -name: Full CI - -on: - push: - branches: [ main ] - schedule: - - cron: '0 0 * * 0' # Weekly on Sunday - -jobs: - ci-full: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.4' - coverage: xdebug - - - name: Install dependencies - run: make install - - - name: Run Full CI - run: make ci-full - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 - with: - files: ./coverage/clover.xml - fail_ci_if_error: true - - - name: Upload mutation report - if: always() - uses: actions/upload-artifact@v3 - with: - name: infection-report - path: infection.html -``` - -#### Release Workflow -```yaml -# .github/workflows/release.yml -name: Release - -on: - push: - tags: - - 'v*.*.*' - -jobs: - release: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.4' - - - name: Install dependencies - run: make install - - - name: Run CD pipeline - run: make cd - - - name: Create GitHub Release - uses: softprops/action-gh-release@v1 - with: - files: | - coverage/clover.xml - infection.html - build/benchmarks/last.txt - generate_release_notes: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -``` - -### GitLab CI -```yaml -# .gitlab-ci.yml -stages: - - validate - - test - - quality - - release - -variables: - COMPOSER_CACHE_DIR: "$CI_PROJECT_DIR/.composer-cache" - -cache: - paths: - - vendor/ - - .composer-cache/ - -# Fast CI (on every commit) -ci: - stage: test - image: kariricode/php-api-stack:dev - script: - - make install - - make ci - artifacts: - reports: - junit: build/reports/junit.xml - only: - - merge_requests - - branches - -# Full CI (on main branch) -ci-full: - stage: quality - image: kariricode/php-api-stack:dev - script: - - make install - - make ci-full - coverage: '/Lines:\s+(\d+\.\d+)%/' - artifacts: - reports: - coverage_report: - coverage_format: cobertura - path: coverage/clover.xml - paths: - - coverage/ - - infection.html - expire_in: 30 days - only: - - main - -# Release preparation -cd: - stage: release - image: kariricode/php-api-stack:dev - script: - - make install - - make cd - artifacts: - paths: - - build/benchmarks/ - only: - - tags -``` - -### Jenkins Pipeline -```groovy -// Jenkinsfile -pipeline { - agent any - - environment { - COMPOSER_HOME = "${WORKSPACE}/.composer" - } - - stages { - stage('Setup') { - steps { - sh 'make check-php' - sh 'make install' - } - } - - stage('Fast CI') { - when { - not { branch 'main' } - } - steps { - sh 'make ci' - } - } - - stage('Full CI') { - when { branch 'main' } - steps { - sh 'make ci-full' - } - post { - always { - junit 'build/reports/junit.xml' - publishHTML([ - reportDir: 'coverage/html', - reportFiles: 'index.html', - reportName: 'Coverage Report' - ]) - } - } - } - - stage('CD') { - when { tag "v*" } - steps { - sh 'make cd' - } - } - } - - post { - failure { - mail to: 'team@example.com', - subject: "Build Failed: ${env.JOB_NAME} - ${env.BUILD_NUMBER}", - body: "Check console output at ${env.BUILD_URL}" - } - } -} -``` - ---- - -## Pipeline Optimization - -### Parallel Execution -```makefile -# Example: Parallel static analysis -analyse-parallel: - @echo "Running static analysis in parallel..." - @$(PHPSTAN) analyse src --level=max & \ - PHPSTAN_PID=$$!; \ - $(PSALM) --show-info=true --stats --no-cache & \ - PSALM_PID=$$!; \ - wait $$PHPSTAN_PID $$PSALM_PID - @echo "✓ Analysis complete" -``` - -### Caching Strategy -```yaml -# GitHub Actions cache example -- name: Cache tools - uses: actions/cache@v3 - with: - path: | - ~/.composer/cache - var/cache/phpstan - .php-cs-fixer.cache - key: ${{ runner.os }}-tools-${{ hashFiles('**/composer.lock') }} -``` - -### Incremental Testing -```bash -# Test only changed files -CHANGED_FILES=$(git diff --name-only main...HEAD | grep '\.php$') -vendor/bin/phpunit $(echo $CHANGED_FILES | sed 's/src/tests/g') -``` - -### Skip Slow Tests in CI -```xml - - - - slow - integration - - -``` -```bash -# Fast CI: Skip slow tests -make test # Excludes @slow and @integration - -# Full CI: Include all tests -vendor/bin/phpunit --group slow,integration -``` - ---- - -## Best Practices - -### 1. Pipeline Selection -```bash -# Development (every commit) -make pre-commit # 30-60s - -# Pull request -make ci # 1-2 min - -# Main branch merge -make ci-full # 3-5 min - -# Release -make cd # 5-8 min -``` - -### 2. Fail-Fast Principle -```bash -# ✅ Good: Stop on first failure -make ci -# Lint fails → STOP (no point running tests) - -# ❌ Bad: Continue despite errors -make lint || true -make analyse || true -make test -# Wastes time, unclear which check failed -``` - -### 3. Feedback Speed Optimization -```bash -# Order checks by speed (fast → slow) -1. lint (5-10s) ← Fast feedback -2. cs-check (10-20s) -3. phpstan (30-60s) -4. psalm (30-60s) -5. test (1-2min) -6. coverage (2-3min) ← Slow, run less often -7. mutation (3-5min) -``` - -### 4. Branch Protection Rules - -**GitHub Branch Protection:** -``` -Settings → Branches → main -├─ Require status checks to pass -│ ├─ CI (required) -│ ├─ Full CI (optional) -│ └─ CD (for tags only) -├─ Require conversation resolution -└─ Require linear history -``` - -### 5. Badge Integration -```markdown -# README.md -![CI](https://github.com/user/repo/workflows/CI/badge.svg) -![Coverage](https://codecov.io/gh/user/repo/branch/main/graph/badge.svg) -![Mutation](https://img.shields.io/endpoint?url=https://badge-api.stryker-mutator.io/github.com/user/repo/main) -``` - -### 6. Quality Metrics Tracking -```bash -# Weekly quality report -{ - echo "Quality Report - $(date)" - echo "================================" - make ci-full 2>&1 | tee quality-report.txt - - echo "" - echo "Coverage:" - grep "Lines:" quality-report.txt - - echo "" - echo "Mutation Score:" - grep "MSI:" quality-report.txt -} | mail -s "Weekly Quality Report" team@example.com -``` - ---- - -## Command Reference - -### Pipeline Commands -```bash -make check # Alias for 'ci' -make ci # Fast CI pipeline (~1-2 min) -make ci-full # Full CI pipeline (~3-5 min) -make cd # CD pipeline (~5-8 min) -make pre-commit # Pre-commit checks (~30-60s) -``` - -### Git Hook Management -```bash -make git-hooks-setup # Install pre-commit hook -make git-hooks-check # Verify hook installation -make git-hooks-remove # Remove and restore backup -``` - ---- - -## Quick Reference Card -``` -╔═══════════════════════════════════════════════════════════╗ -║ CI/CD Pipeline Quick Reference ║ -╠═══════════════════════════════════════════════════════════╣ -║ PRE-COMMIT │ make pre-commit (~30-60s) ║ -║ FAST CI │ make ci (~1-2 min) ║ -║ FULL CI │ make ci-full (~3-5 min) ║ -║ CD RELEASE │ make cd (~5-8 min) ║ -║──────────────┼───────────────────────────────────────────║ -║ GIT HOOKS │ make git-hooks-setup ║ -║ VERIFY HOOKS │ make git-hooks-check ║ -║ REMOVE HOOKS │ make git-hooks-remove ║ -╚═══════════════════════════════════════════════════════════╝ - -Pipeline Selection: - Local development: make pre-commit - Pull request: make ci - Main branch: make ci-full - Release tag: make cd - -Execution Order (fail-fast): - 1. PHP version → 2. Lint → 3. Style → 4. Analysis → 5. Tests -``` - ---- - -**Version**: 1.0.0 -**Module**: `Makefile.orchestration.mk` -**Maintainer**: Walmir Silva diff --git a/.docs/MAKEFILE-qa.md b/.docs/MAKEFILE-qa.md deleted file mode 100644 index c45f884..0000000 --- a/.docs/MAKEFILE-qa.md +++ /dev/null @@ -1,1279 +0,0 @@ -
- -# Quality Assurance - -[![KaririCode](https://img.shields.io/badge/KaririCode-DevKit-6D00CC?style=for-the-badge)](https://github.com/KaririCode-Framework/kariricode-devkit) -[![PHPUnit](https://img.shields.io/badge/PHPUnit-11.x-3776AB?style=for-the-badge&logo=php)](https://phpunit.de) -[![PHPStan](https://img.shields.io/badge/PHPStan-Level%20Max-4F5B93?style=for-the-badge)](https://phpstan.org) -[![Psalm](https://img.shields.io/badge/Psalm-Type%20Safety-8A2BE2?style=for-the-badge)](https://psalm.dev) - -**kariricode/devkit** - Professional development environment for KaririCode Framework - -Part of the [KaririCode Framework](https://kariricode.org) ecosystem - -[Main Documentation](MAKEFILE.md) | [GitHub Repository](https://github.com/KaririCode-Framework/kariricode-devkit) - -
- ---- - -## Table of Contents - -1. [Overview](#overview) -2. [Testing](#testing) -3. [Code Linting](#code-linting) -4. [Static Analysis](#static-analysis) -5. [Code Style & Formatting](#code-style--formatting) -6. [Code Coverage](#code-coverage) -7. [Mutation Testing](#mutation-testing) -8. [Troubleshooting](#troubleshooting) -9. [Best Practices](#best-practices) - ---- - -## Overview - -### Scope - -The QA module (`Makefile.qa.mk`) provides comprehensive quality assurance tools: -- **Testing**: PHPUnit test execution (unit, integration, functional) -- **Linting**: PHP syntax validation -- **Static Analysis**: PHPStan and Psalm -- **Code Style**: PHPCS and PHP-CS-Fixer -- **Coverage**: Code coverage reporting -- **Mutation**: Infection mutation testing - -### Quality Gates -``` -┌─────────────────────────────────────────────────────────┐ -│ Quality Pipeline │ -├─────────────────────────────────────────────────────────┤ -│ │ -│ 1. LINT → Syntax errors? → FAIL │ -│ 2. CS-CHECK → Style violations? → FAIL │ -│ 3. PHPSTAN → Type errors? → FAIL │ -│ 4. PSALM → Logic errors? → FAIL │ -│ 5. TEST → Tests failing? → FAIL │ -│ 6. COVERAGE → Coverage < 80%? → WARN │ -│ 7. MUTATION → MSI < 80%? → WARN │ -│ │ -│ ✓ ALL PASSED → Ready for production │ -└─────────────────────────────────────────────────────────┘ -``` - -### Tools Configuration - -| Tool | Config File | Purpose | -|------|-------------|---------| -| PHPUnit | `phpunit.xml` | Test execution | -| PHPStan | `phpstan.neon` | Static analysis (level max) | -| Psalm | `psalm.xml` | Type checking & taint analysis | -| PHPCS | `phpcs.xml` | PSR-12 style checking | -| PHP-CS-Fixer | `.php-cs-fixer.php` | Modern PHP style fixing | -| Infection | `infection.json` | Mutation testing | -| PHPBench | `phpbench.json` | Performance benchmarking | - ---- - -## Testing - -### Test Execution - -#### Run All Tests -```bash -make test - -# Executes: -# vendor/bin/phpunit --colors=always --testdox - -# Output: -# PHPUnit 11.5.2 by Sebastian Bergmann and contributors. -# -# Parser\Lexer (KaririCode\Parser\Tests\Unit\Lexer) -# ✔ Tokenizes simple string -# ✔ Handles whitespace correctly -# ✔ Recognizes keywords -# -# Time: 00:01.234, Memory: 12.00 MB -# -# OK (150 tests, 420 assertions) -``` - -**Options:** -- `--colors=always`: Colored output -- `--testdox`: Human-readable test names - -#### Test Suites -```bash -# Unit tests only (fast, isolated) -make test-unit - -# Integration tests (with dependencies) -make test-integration - -# Functional tests (end-to-end) -make test-functional -``` - -**Test Suite Configuration (phpunit.xml):** -```xml - - - tests/Unit - - - tests/Integration - - - tests/Functional - - -``` - -### Test Organization - -#### Directory Structure -``` -tests/ -├── Unit/ # Pure unit tests -│ ├── Lexer/ -│ │ ├── LexerTest.php -│ │ └── TokenTest.php -│ ├── Parser/ -│ │ └── ParserTest.php -│ └── ... -│ -├── Integration/ # Component integration -│ ├── LexerParserTest.php -│ └── ... -│ -├── Functional/ # End-to-end scenarios -│ ├── CompleteParsingTest.php -│ └── ... -│ -├── Fixtures/ # Test data -│ ├── valid_code.php -│ └── invalid_code.php -│ -└── bootstrap.php # Test initialization -``` - -#### Test Naming Conventions -```php -// ✅ Good: Descriptive, follows conventions -class LexerTest extends TestCase -{ - public function testTokenizesSimpleString(): void - { - // Arrange - $lexer = new Lexer(); - - // Act - $tokens = $lexer->tokenize('assertCount(5, $tokens); - } -} - -// ❌ Bad: Vague test name -class Test1 extends TestCase -{ - public function test(): void - { - $this->assertTrue(true); - } -} -``` - -### Test Execution Modes - -#### Standard Mode -```bash -# Default: All tests with testdox output -make test -``` - -#### Verbose Mode -```bash -# Detailed output for debugging -vendor/bin/phpunit --testdox --verbose -``` - -#### Stop on Failure -```bash -# Stop at first failure -vendor/bin/phpunit --stop-on-failure -``` - -#### Filter Specific Tests -```bash -# By test method name -vendor/bin/phpunit --filter testTokenizesSimpleString - -# By class name -vendor/bin/phpunit --filter LexerTest - -# By namespace pattern -vendor/bin/phpunit --filter "Parser\\Lexer" -``` - ---- - -## Code Linting - -### Syntax Validation -```bash -make lint - -# Executes: -# find src tests -name "*.php" -print0 | xargs -0 -n1 php -l > /dev/null - -# Checks: -# - PHP syntax errors -# - Parse errors -# - Fatal syntax violations - -# Output: -# → Linting PHP files... -# ✓ All PHP files are valid -``` - -### What Lint Catches - -**Valid Code:** -```php -children; -} - -// ✅ Fixed -public function getChildren(): array -{ - return $this->children; -} -``` - -**Issue: Undefined Property** -```php -// ❌ PHPStan error -class Node -{ - public function __construct() - { - $this->name = 'default'; // Property not declared - } -} - -// ✅ Fixed -class Node -{ - private string $name; - - public function __construct() - { - $this->name = 'default'; - } -} -``` - -**Issue: Possibly Null Reference** -```php -// ❌ PHPStan error -public function process(?Node $node): string -{ - return $node->getName(); // $node might be null -} - -// ✅ Fixed (Option 1: Guard) -public function process(?Node $node): string -{ - if ($node === null) { - throw new \InvalidArgumentException('Node cannot be null'); - } - return $node->getName(); -} - -// ✅ Fixed (Option 2: Null coalescing) -public function process(?Node $node): string -{ - return $node?->getName() ?? 'default'; -} -``` - -### Psalm - -#### Basic Analysis -```bash -make psalm - -# Executes: -# vendor/bin/psalm --show-info=true --stats --no-cache - -# Configuration (psalm.xml): - - - - - -``` - -#### Output Example -```bash -$ make psalm - -→ Running Psalm... - -Scanning files... -Analyzing files... - -████████████████████████████████████████████ 100% - ------------------------------- - ISSUES FOUND ------------------------------- - -ERROR: UnimplementedAbstractMethod - src/Parser/Node/GreenTree/Expression.php:15 - Class KaririCode\Parser\Node\GreenTree\Expression\BinaryExpression does not - implement abstract method getWidthWithoutTrivia - -INFO: MixedReturnStatement - src/Parser/Lexer.php:89 - Could not infer return type from $tokens - ------------------------------- - -81 errors found -99.34% type coverage -Psalm can help you fix these errors - -✓ Psalm analysis complete (with errors) -``` - -#### Error Levels - -| Level | Strictness | Description | -|-------|------------|-------------| -| 1 | Maximum | Catches almost everything | -| 2 | High | Practical maximum for most projects | -| **3** | **Medium** | **Recommended default** | -| 4 | Low | Loose type checking | -| 5+ | Very low | Minimal checks | - -#### Generate Baseline -```bash -make psalm-baseline - -# Creates psalm-baseline.xml -# Similar to PHPStan baseline -``` - -#### Taint Analysis (Security) -```bash -make psalm-taint - -# Detects security vulnerabilities: -# - SQL injection -# - XSS vulnerabilities -# - Command injection -# - Path traversal - -# Requires taint sources/sinks annotation -``` - -**Taint Analysis Example:** -```php -/** - * @psalm-taint-source input - */ -public function getUserInput(): string -{ - return $_GET['name']; -} - -/** - * @psalm-taint-sink html - */ -public function renderHTML(string $html): void -{ - echo $html; // Psalm warns: untrusted data in HTML context -} - -// Usage: -$input = $this->getUserInput(); -$this->renderHTML($input); // ⚠️ Taint detected! -``` - -#### Common Psalm Issues - -**Issue: Mixed Type** -```php -// ❌ Psalm error: MixedReturnStatement -public function getData() -{ - return $this->data; // Type unknown -} - -// ✅ Fixed -/** @return array */ -public function getData(): array -{ - return $this->data; -} -``` - -**Issue: Unimplemented Abstract Method** -```php -// ❌ Psalm error -abstract class Node -{ - abstract public function getChildren(): array; -} - -class Leaf extends Node -{ - // Missing implementation -} - -// ✅ Fixed -class Leaf extends Node -{ - public function getChildren(): array - { - return []; - } -} -``` - -### Combined Analysis -```bash -make analyse - -# Runs all static analysis tools: -# 1. PHPStan -# 2. Psalm -# 3. cs-check - -# Stops on first failure -# Use in pre-commit hooks -``` - ---- - -## Code Style & Formatting - -### Check Code Style - -#### PHPCS (PHP_CodeSniffer) -```bash -make cs-check - -# Executes: -# vendor/bin/phpcs --standard=phpcs.xml --colors src tests - -# Checks: -# - PSR-12 compliance -# - Naming conventions -# - Indentation -# - Line length -# - Spacing - -# Output: -FILE: /path/to/File.php ----------------------------------------------------------------------- -FOUND 5 ERRORS AFFECTING 3 LINES ----------------------------------------------------------------------- - 12 | ERROR | Line exceeds 120 characters - 15 | ERROR | Expected 1 space after comma; 0 found - 20 | ERROR | Missing doc comment for function ----------------------------------------------------------------------- -``` - -#### Configuration (phpcs.xml) -```xml - - - KaririCode Coding Standard - - - - - - - - - - - - - - src - tests - - - */vendor/* - */cache/* - -``` - -### Auto-fix Code Style - -#### Two-Stage Fixing -```bash -make format - -# Stage 1: PHP-CS-Fixer (modern PHP features) -# Stage 2: PHPCBF (PSR-12 compliance) - -# Output: -# → Formatting code... -# -# Loaded config default. -# Using cache file ".php-cs-fixer.cache". -# -# Fixed 12 files in 3.456 seconds, 18.00 MB memory used -# -# PHPCBF RESULT SUMMARY -# ---------------------------------------------------------------------- -# A TOTAL OF 5 FILES WERE FIXED -# ---------------------------------------------------------------------- -# -# ✓ Code formatted -``` - -#### PHP-CS-Fixer Configuration -```php -// .php-cs-fixer.php -in(__DIR__ . '/src') - ->in(__DIR__ . '/tests') - ->exclude('vendor') - ->name('*.php'); - -return (new Config()) - ->setRules([ - '@PSR12' => true, - '@PHP84Migration' => true, - 'array_syntax' => ['syntax' => 'short'], - 'declare_strict_types' => true, - 'native_function_invocation' => [ - 'include' => ['@all'], - ], - 'ordered_imports' => [ - 'imports_order' => ['class', 'function', 'const'], - ], - 'no_unused_imports' => true, - ]) - ->setFinder($finder); -``` - -#### Preview Changes Without Applying -```bash -make format-dry - -# Shows diff without modifying files -# Review before applying changes -``` - -#### What Gets Fixed - -**Before Formatting:** -```php - - - - src - - - - - - - -``` - -### Coverage Thresholds -```xml - - - - - - - - - -``` - ---- - -## Mutation Testing - -### Run Mutation Tests -```bash -make mutation - -# Executes: -# XDEBUG_MODE=coverage vendor/bin/infection \ -# --threads=4 \ -# --min-msi=80 \ -# --min-covered-msi=90 \ -# --show-mutations - -# Requirements: -# - MSI (Mutation Score Indicator): ≥80% -# - Covered MSI: ≥90% - -# Output: -You are running Infection with xdebug enabled. - ____ ____ __ _ - / _/___ / __/__ _____/ /_(_)___ ____ - / // __ \/ /_/ _ \/ ___/ __/ / __ \/ __ \ - _/ // / / / __/ __/ /__/ /_/ / /_/ / / / / -/___/_/ /_/_/ \___/\___/\__/_/\____/_/ /_/ - -Running initial test suite... - -PHPUnit version: 11.5.2 - - 24 [============================] < 1 sec - -Generate mutants... - -Processing source code files: 21/21 -Creating mutated files and processes: 245/245 -.S.SSSS.S.S.S.S.S.S.S.S....S.S..S.S.S....S. - -245 mutations were generated: - 187 mutants were killed - 42 mutants were not covered by tests - 16 covered mutants were not detected - -Metrics: - Mutation Score Indicator (MSI): 82% - Mutation Code Coverage: 83% - Covered Code MSI: 92% -``` - -### Generate Detailed Report -```bash -make mutation-report - -# Generates infection.html -# Detailed mutation analysis -# Shows which mutations survived -``` - -### Mutation Types - -| Mutator | Description | Example | -|---------|-------------|---------| -| **Binary** | Changes operators | `+` → `-`, `&&` → `\|\|` | -| **Comparison** | Alters comparisons | `>` → `>=`, `==` → `!=` | -| **Increment** | Modifies increments | `++` → `--` | -| **Return Value** | Changes returns | `return true` → `return false` | -| **Array** | Mutates arrays | `[]` → `[null]` | -| **Function Call** | Removes calls | `trim($x)` → `$x` | - -### Mutation Testing Example - -**Original Code:** -```php -public function isPositive(int $number): bool -{ - return $number > 0; -} -``` - -**Test:** -```php -public function testIsPositive(): void -{ - $this->assertTrue($this->calculator->isPositive(5)); -} -``` - -**Mutation (survived):** -```php -// Mutator: GreaterThan → GreaterThanOrEqual -public function isPositive(int $number): bool -{ - return $number >= 0; // Mutation: > changed to >= -} -``` - -**Why it survived:** Test doesn't check boundary (0) - -**Better Test:** -```php -public function testIsPositive(): void -{ - $this->assertTrue($this->calculator->isPositive(5)); - $this->assertTrue($this->calculator->isPositive(1)); - $this->assertFalse($this->calculator->isPositive(0)); // ✅ Kills mutation - $this->assertFalse($this->calculator->isPositive(-5)); -} -``` - ---- - -## Troubleshooting - -### Issue 1: Tests Not Executing - -**Symptoms:** -```bash -$ make test - -There was 1 PHPUnit test runner warning: -1) XDEBUG_MODE=coverage has to be set -No tests executed! -``` - -**Cause:** Xdebug warning mistaken for error - -**Solutions:** - -**Option A: Disable Xdebug** -```bash -export XDEBUG_MODE=off -make test -``` - -**Option B: Use PHP INI** -```bash -php -d xdebug.mode=off vendor/bin/phpunit -``` - -**Option C: Docker (recommended)** -```bash -make docker-test -# Docker container has proper configuration -``` - -### Issue 2: PHPStan Memory Limit - -**Symptoms:** -```bash -$ make phpstan - -Fatal error: Allowed memory size of 134217728 bytes exhausted -``` - -**Solutions:** - -**Option A: Increase in Makefile (already done)** -```makefile -# Makefile.qa.mk already sets --memory-limit=512M -phpstan: - @$(PHPSTAN) analyse src --level=max --memory-limit=512M -``` - -**Option B: Increase further if needed** -```bash -vendor/bin/phpstan analyse src --level=max --memory-limit=1G -``` - -**Option C: Use PHP memory limit** -```bash -php -d memory_limit=1G vendor/bin/phpstan analyse src --level=max -``` - -### Issue 3: Psalm Cache Issues - -**Symptoms:** -```bash -$ make psalm - -InvalidArgumentException: Cache directory does not exist -``` - -**Solutions:** -```bash -# Clear Psalm cache -rm -rf var/cache/psalm - -# Run with --no-cache -vendor/bin/psalm --no-cache - -# Or use Makefile target (already includes --no-cache) -make psalm -``` - -### Issue 4: PHP-CS-Fixer Permission Denied - -**Symptoms:** -```bash -$ make format - -Permission denied: .php-cs-fixer.cache -``` - -**Solutions:** -```bash -# Remove cache file -rm .php-cs-fixer.cache - -# Fix permissions -chmod 644 .php-cs-fixer.cache - -# Or run without cache -vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php --no-cache -``` - -### Issue 5: Coverage Generation Fails - -**Symptoms:** -```bash -$ make coverage - -PHP Fatal error: Xdebug is required for code coverage -``` - -**Solutions:** - -**Check Xdebug Installation:** -```bash -php -m | grep xdebug - -# If missing, install: -# Ubuntu/Debian -sudo apt install php8.4-xdebug - -# macOS (Homebrew) -brew install php@8.4 -pecl install xdebug -``` - -**Verify Configuration:** -```bash -php -i | grep xdebug.mode -# Should show: xdebug.mode => coverage => coverage -``` - -**Use Docker (recommended):** -```bash -make docker-coverage -# Pre-configured with Xdebug -``` - ---- - -## Best Practices - -### 1. Pre-Commit Quality Checks -```bash -# Install git hook -make git-hooks-setup - -# Or run manually before commit -make pre-commit - -# Runs: -# 1. format → Auto-fix style -# 2. lint → Syntax check -# 3. analyse → Static analysis -# 4. test-unit → Fast tests -``` - -### 2. Incremental Quality Improvement -```bash -# Week 1: Generate baselines -make phpstan-baseline -make psalm-baseline - -# Week 2-4: Fix issues incrementally -# (edit files, fix 10-20 issues per day) - -# Week 5: Regenerate baselines -make phpstan-baseline -make psalm-baseline - -# Track progress -git diff phpstan-baseline.neon -``` - -### 3. Test-Driven Development -```bash -# 1. Write failing test -vendor/bin/phpunit --filter testNewFeature -# ✗ FAILURES! - -# 2. Implement feature -# (edit source) - -# 3. Run test again -vendor/bin/phpunit --filter testNewFeature -# ✓ OK - -# 4. Run full suite -make test -``` - -### 4. Coverage-Driven Testing -```bash -# 1. Generate coverage -make coverage - -# 2. Open coverage/html/index.html -# Identify uncovered lines (red) - -# 3. Write tests for uncovered code - -# 4. Verify coverage improved -make coverage-text -``` - -### 5. Mutation-Driven Test Quality -```bash -# 1. Run mutation testing -make mutation - -# 2. Review survived mutants in infection.html - -# 3. Write tests to kill mutants - -# 4. Re-run until MSI ≥ 80% -make mutation -``` - -### 6. CI Integration Strategy -```yaml -# .github/workflows/qa.yml -name: Quality Assurance - -on: [push, pull_request] - -jobs: - qa: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.4' - coverage: xdebug - - # Fast checks first - - name: Install - run: make install - - - name: Lint - run: make lint - - - name: Code Style - run: make cs-check - - - name: Static Analysis - run: make analyse - - - name: Tests - run: make test - - # Slow checks (if fast checks pass) - - name: Coverage - if: success() - run: make coverage - - - name: Mutation - if: success() - run: make mutation -``` - ---- - -## Command Reference - -### Testing -```bash -make test # Run all tests -make test-unit # Unit tests only -make test-integration # Integration tests -make test-functional # Functional tests -``` - -### Linting & Analysis -```bash -make lint # PHP syntax validation -make phpstan # PHPStan static analysis -make phpstan-baseline # Generate PHPStan baseline -make psalm # Psalm type checking -make psalm-baseline # Generate Psalm baseline -make psalm-taint # Security taint analysis -make analyse # All static analysis -``` - -### Code Style -```bash -make cs-check # Check code style (PHPCS) -make cbf-fix # Fix code style (PHPCBF) -make format # Auto-fix all style issues -make format-dry # Preview formatting changes -``` - -### Coverage & Mutation -```bash -make coverage # Generate coverage report -make coverage-text # Terminal coverage -make mutation # Mutation testing -make mutation-report # Detailed mutation report -``` - ---- - -## Quick Reference Card -``` -╔═══════════════════════════════════════════════════════════╗ -║ Quality Assurance Quick Reference ║ -╠═══════════════════════════════════════════════════════════╣ -║ LINT │ make lint ║ -║ FORMAT │ make format ║ -║ ANALYSE │ make analyse ║ -║ TEST │ make test ║ -║──────────────┼────────────────────────────────────────────║ -║ UNIT TESTS │ make test-unit ║ -║ COVERAGE │ make coverage ║ -║ MUTATION │ make mutation ║ -║──────────────┼────────────────────────────────────────────║ -║ PHPSTAN │ make phpstan ║ -║ PSALM │ make psalm ║ -║ CS CHECK │ make cs-check ║ -╚═══════════════════════════════════════════════════════════╝ - -Daily Workflow: - make format → make lint → make test-unit - -Pre-Commit: - make pre-commit (format + lint + analyse + test-unit) - -Full QA: - make analyse → make test → make coverage → make mutation -``` - ---- - -**Version**: 1.0.0 -**Module**: `Makefile.qa.mk` -**Maintainer**: Walmir Silva diff --git a/.docs/MAKEFILE-setup.md b/.docs/MAKEFILE-setup.md deleted file mode 100644 index 4a598f2..0000000 --- a/.docs/MAKEFILE-setup.md +++ /dev/null @@ -1,1006 +0,0 @@ -
- -# Setup & Installation - -[![KaririCode](https://img.shields.io/badge/KaririCode-DevKit-6D00CC?style=for-the-badge)](https://github.com/KaririCode-Framework/kariricode-devkit) -[![PHP](https://img.shields.io/badge/PHP-8.4%2B-777BB4?style=for-the-badge&logo=php)](https://www.php.net) -[![Composer](https://img.shields.io/badge/Composer-2.x-885630?style=for-the-badge&logo=composer)](https://getcomposer.org) - -**kariricode/devkit** - Professional development environment for KaririCode Framework - -Part of the [KaririCode Framework](https://kariricode.org) ecosystem - -[Main Documentation](MAKEFILE.md) | [GitHub Repository](https://github.com/KaririCode-Framework/kariricode-devkit) - -
- ---- - -## Table of Contents - -1. [Overview](#overview) -2. [Prerequisites](#prerequisites) -3. [Installation Methods](#installation-methods) -4. [Dependency Management](#dependency-management) -5. [Validation & Security](#validation--security) -6. [Cleanup Operations](#cleanup-operations) -7. [Troubleshooting](#troubleshooting) -8. [Best Practices](#best-practices) - ---- - -## Overview - -### Scope - -The setup module (`Makefile.setup.mk`) provides targets for: -- **PHP version validation** (8.4.0+ enforcement) -- **Dependency installation** (Composer-based) -- **Security auditing** (vulnerability scanning) -- **Environment verification** (tool availability) -- **Cleanup operations** (artifact removal) - -### Module Architecture -``` -Setup Module (.make/local/Makefile.setup.mk) -│ -├── Prerequisites -│ └── check-php → PHP version validation -│ -├── Installation -│ ├── install → Standard installation -│ ├── install-dev → Development installation -│ ├── fresh-install → Clean installation -│ └── update → Dependency updates -│ -├── Validation -│ ├── verify-install → Post-install verification -│ ├── validate → composer.json validation -│ ├── security → Vulnerability scan -│ ├── security-strict → Strict security audit -│ └── outdated → Outdated dependency check -│ -└── Cleanup - ├── clean → Remove artifacts - └── clean-all → Deep clean + vendor -``` - ---- - -## Prerequisites - -### System Requirements - -#### Minimum Requirements - -| Component | Minimum Version | Verification Command | -|-----------|-----------------|---------------------| -| **PHP** | 8.4.0+ | `php -v` | -| **Composer** | 2.0+ | `composer --version` | -| **Git** | 2.0+ | `git --version` | -| **Make** | 3.81+ | `make --version` | - -#### Required PHP Extensions -```bash -# Check installed extensions -php -m - -# Required: -- json # JSON processing -- mbstring # Multibyte string support -- xml # XML parsing -- tokenizer # PHP tokenization -- pcre # Regular expressions - -# Recommended: -- opcache # Performance optimization -- xdebug # Debugging & coverage -- redis # Caching support -``` - -### Verification - -#### Check PHP Version -```bash -# Validate PHP version requirement -make check-php - -# Output: -# → Checking PHP version... -# ✓ PHP version 8.4.14 OK (>= 8.4.0) -``` - -**Version Comparison Logic:** -```makefile -# From Makefile.functions.mk -CURRENT_VERSION="8.4.14" -MIN_VERSION="8.4.0" -LOWEST=$(printf '%s\n%s' "$MIN_VERSION" "$CURRENT_VERSION" | sort -V | head -n1) - -if [ "$LOWEST" != "$MIN_VERSION" ]; then - echo "✗ PHP 8.4.0+ required, found $CURRENT_VERSION" - exit 1 -fi -``` - -#### Debug Composer Configuration -```bash -# Show Composer paths and availability -make debug-composer - -# Output: -# COMPOSER_BIN = '/usr/local/bin/composer' -# COMPOSER = '/usr/local/bin/composer' -# Composer version 2.8.4 2024-12-12 - -# If not found: -# COMPOSER_BIN = '' -# COMPOSER = 'composer' -# Composer not found with command -v -``` - -#### Environment Information -```bash -# Show complete environment info -make info - -# Output: -# ╔════════════════════════════════════════════════════════╗ -# Project Information -# ──────────────────────────────────────────────────────── -# PHP Version: 8.4.14 -# PHP Binary: /usr/bin/php -# Composer: /usr/local/bin/composer -# Project Directory: /home/user/kariricode-devkit -# Source Directory: src -# Test Directory: tests -# -# Installed Tools -# ──────────────────────────────────────────────────────── -# PHPUnit: ✓ -# PHPStan: ✓ -# Psalm: ✓ -# PHPCS: ✓ -# PHP-CS-Fixer: ✓ -# Infection: ✓ -# PHPBench: ✓ -# ╚════════════════════════════════════════════════════════╝ -``` - ---- - -## Installation Methods - -### Standard Installation - -#### When to Use -- **First time setup** from existing `composer.lock` -- **After cloning** repository -- **Restoring** from version control - -#### Command -```bash -make install - -# Workflow: -# 1. Check PHP version (make check-php) -# 2. Validate composer.json -# 3. Install from composer.lock (if valid) -# 4. OR update if lock file outdated -# 5. Optimize autoloader -# 6. Verify installation -``` - -#### Output Example -```bash -$ make install - -→ Checking PHP version... -✓ PHP version 8.4.14 OK (>= 8.4.0) - -→ Installing Composer dependencies... -Loading composer repositories with package information -Installing dependencies from lock file (including require-dev) -Package operations: 42 installs, 0 updates, 0 removals - - Installing symfony/polyfill-php80 (v1.28.0): Extracting archive - - Installing psr/container (2.0.2): Extracting archive - ... -Generating optimized autoload files -✓ Installation complete -``` - -#### What Gets Installed -```bash -# Production dependencies -composer.json → require -├── php: ^8.4 -└── kariricode/* - -# Development tools -composer.json → require-dev -├── phpunit/phpunit: ^11.0 -├── phpstan/phpstan: ^2.0 -├── vimeo/psalm: ^5.0 -├── squizlabs/php_codesniffer: ^3.7 -├── friendsofphp/php-cs-fixer: ^3.0 -└── infection/infection: ^0.27 -``` - -### Development Installation - -#### When to Use -- **Development environment** setup -- **Contributing** to the project -- **Need all tools** for QA - -#### Command -```bash -make install-dev - -# Differences from 'install': -# - Preserves composer.lock without validation -# - Installs ALL dev dependencies -# - No autoloader optimization -# - Keeps debugging info -``` - -#### Use Case -```bash -# Scenario: Setting up development environment -git clone https://github.com/KaririCode-Framework/kariricode-devkit.git -cd kariricode-devkit - -# Install with all dev tools -make install-dev - -# Verify tools are available -make info -# All tools should show ✓ -``` - -### Fresh Installation - -#### When to Use -- **Corrupted** `composer.lock` -- **Dependency conflicts** -- **Major version updates** -- **Clean slate** needed - -#### Command -```bash -make fresh-install - -# Workflow: -# 1. Remove composer.lock -# 2. Install fresh dependencies -# 3. Generate new lock file -# 4. Optimize autoloader -# 5. Verify installation -``` - -⚠️ **Warning**: This regenerates `composer.lock` and may update packages to newer versions within version constraints. - -#### Output Example -```bash -$ make fresh-install - -→ Removing composer.lock... -→ Installing fresh dependencies... -No composer.lock file present. Updating dependencies to latest version. -Loading composer repositories with package information -Updating dependencies -Lock file operations: 42 installs, 0 updates, 0 removals - - Locking phpunit/phpunit (11.5.2) - - Locking phpstan/phpstan (2.1.5) - ... -Writing lock file -✓ Fresh installation complete -``` - -#### When to Commit New Lock File -```bash -# Review changes -git diff composer.lock - -# If intentional update: -git add composer.lock -git commit -m "chore: regenerate composer.lock" - -# If accidental: -git checkout composer.lock -make install # Restore from existing lock -``` - ---- - -## Dependency Management - -### Update Dependencies - -#### Update All Dependencies -```bash -make update - -# Executes: -# composer update --with-all-dependencies --no-interaction --prefer-dist --optimize-autoloader - -# Updates: -# - Direct dependencies -# - Transitive dependencies -# - Respects version constraints in composer.json -``` - -#### Update Specific Package -```bash -# Use composer directly -make exec-php CMD="composer update vendor/package" - -# Example: -make exec-php CMD="composer update phpunit/phpunit" -``` - -#### Update with Constraints -```bash -# Update within patch versions -make exec-php CMD="composer update phpunit/phpunit --prefer-lowest" - -# Update with stability -make exec-php CMD="composer update --prefer-stable" -``` - -### Add New Dependencies -```bash -# Production dependency -make exec-php CMD="composer require vendor/package" - -# Development dependency -make exec-php CMD="composer require --dev vendor/package" - -# Example: Add new KaririCode component -make exec-php CMD="composer require kariricode/new-component" -``` - -### Remove Dependencies -```bash -# Remove package -make exec-php CMD="composer remove vendor/package" - -# Example: -make exec-php CMD="composer remove phpunit/phpunit" -``` - -### Check Outdated Packages -```bash -make outdated - -# Output shows direct dependencies needing updates: -# ╔════════════════════════════════════════════════════════╗ -# Direct dependencies required in composer.json: -# phpunit/phpunit 11.5.1 → 11.5.2 (patch update) -# phpstan/phpstan 2.1.3 → 2.1.5 (patch update) -# psalm/psalm 5.26.1 → 5.27.0 (minor update) -# ╚════════════════════════════════════════════════════════╝ - -# Ignores indirect dependencies by default -``` - ---- - -## Validation & Security - -### Validate composer.json - -#### Syntax & Structure Validation -```bash -make validate - -# Checks: -# - JSON syntax -# - Schema compliance -# - Required fields (name, description, license) -# - Version constraints syntax -# - PSR-4 autoload mappings - -# Output: -# → Validating composer.json... -# ./composer.json is valid -# ✓ composer.json is valid -``` - -#### Common Validation Errors - -**Invalid JSON:** -```json -{ - "name": "kariricode/devkit" - "description": "Missing comma" ← Error -} -``` - -**Invalid Version Constraint:** -```json -{ - "require": { - "php": "8.4" ← Should be "^8.4" or ">=8.4" - } -} -``` - -**Invalid PSR-4 Namespace:** -```json -{ - "autoload": { - "psr-4": { - "KaririCode\\": "src" ← Missing trailing backslash - } - } -} -``` - -### Security Auditing - -#### Standard Security Check -```bash -make security - -# Executes: -# composer audit --no-dev --locked - -# Checks for: -# - Known security vulnerabilities -# - CVEs in dependencies -# - Abandoned packages (warning only) -``` - -#### Output Examples - -**No Vulnerabilities:** -```bash -$ make security - -→ Checking for security vulnerabilities... -Found 0 security vulnerability advisories affecting 0 packages -✓ No security vulnerabilities found -``` - -**Vulnerabilities Found:** -```bash -$ make security - -→ Checking for security vulnerabilities... -Found 2 security vulnerability advisories affecting 1 package: - -symfony/http-kernel (v6.2.0) -├── CVE-2023-XXXXX (high severity) -│ Fixed in: 6.2.6 -│ Description: Information disclosure vulnerability -└── See: https://github.com/advisories/GHSA-xxxx-yyyy - -✗ Security vulnerabilities found -``` - -**Action Steps:** -```bash -# Update affected package -make exec-php CMD="composer update symfony/http-kernel" - -# Verify fix -make security -``` - -#### Abandoned Packages -```bash -$ make security - -⚠ Found abandoned packages (informational only): -───────────────────────────────────────────────── -Package: vendor/old-package -Replacement: vendor/new-package -───────────────────────────────────────────────── -✓ No security vulnerabilities found -``` - -#### Strict Security Mode -```bash -make security-strict - -# Differences from 'security': -# - Includes dev dependencies -# - Treats abandoned packages as errors -# - More verbose output -# - Fails on any issue - -# Use in: -# - CI/CD pipelines -# - Pre-release checks -# - Security-critical projects -``` - ---- - -## Cleanup Operations - -### Clean Build Artifacts -```bash -make clean - -# Removes: -# ├── build/ (Build outputs) -# ├── coverage/ (Code coverage reports) -# ├── reports/ (Static analysis reports) -# ├── var/cache/ (Application cache) -# ├── .phpunit.cache (PHPUnit cache) -# ├── .phpunit.result.cache -# ├── .php-cs-fixer.cache (CS Fixer cache) -# ├── infection.log (Mutation test logs) -# └── infection.html - -# Does NOT remove: -# - vendor/ (Dependencies) -# - composer.lock (Lock file) -# - Source code -``` - -#### When to Use - -- **Before commit** (remove temporary files) -- **Before benchmarks** (clean state) -- **Disk space** (reclaim space) -- **Fresh start** (consistent state) - -### Deep Clean -```bash -make clean-all - -# Removes everything from 'clean' PLUS: -# ├── vendor/ (All dependencies) -# └── composer.lock (Dependency lock file) - -# Requires 're-install' after: -# make install -``` - -⚠️ **Warning**: This removes `vendor/` and requires re-downloading all dependencies. - -#### When to Use - -- **Before fresh-install** -- **Switching branches** with different dependencies -- **Major PHP version** upgrades -- **Corrupted vendor** directory - -### Cleanup Workflow -```bash -# Daily development cleanup -make clean - -# Weekly deep cleanup -make clean-all -make install - -# Before release -make clean -make ci-full # Ensures clean build -``` - ---- - -## Troubleshooting - -### Issue 1: Composer Not Found - -**Symptoms:** -```bash -$ make install -COMPOSER_BIN = '' -COMPOSER = 'composer' -composer: command not found -``` - -**Diagnosis:** -```bash -make debug-composer -which composer -echo $PATH -``` - -**Solutions:** - -**Option A: Install Composer Globally** -```bash -# Download installer -php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" - -# Verify installer (optional) -php -r "if (hash_file('sha384', 'composer-setup.php') === 'EXPECTED_HASH') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" - -# Install globally -php composer-setup.php --install-dir=/usr/local/bin --filename=composer - -# Cleanup -php -r "unlink('composer-setup.php');" - -# Verify -composer --version -``` - -**Option B: Use Docker** -```bash -# Use Docker for Composer commands -make docker-composer CMD="install" -``` - -### Issue 2: PHP Version Mismatch - -**Symptoms:** -```bash -$ make check-php -✗ PHP 8.4.0+ required, found 8.3.12 -``` - -**Solutions:** - -**Option A: Install PHP 8.4 (Ubuntu/Debian)** -```bash -# Add PPA -sudo add-apt-repository ppa:ondrej/php -sudo apt update - -# Install PHP 8.4 -sudo apt install php8.4 php8.4-cli php8.4-{mbstring,xml,curl} - -# Set as default -sudo update-alternatives --set php /usr/bin/php8.4 - -# Verify -php -v -make check-php -``` - -**Option B: Use Docker** -```bash -# All commands in Docker with PHP 8.4 -make docker-install -make docker-ci -``` - -### Issue 3: Memory Limit Errors - -**Symptoms:** -```bash -$ make install -Fatal error: Allowed memory size of 134217728 bytes exhausted -``` - -**Solutions:** - -**Option A: Increase PHP Memory Limit** -```bash -# Temporary (single command) -php -d memory_limit=512M $(which composer) install - -# Permanent (php.ini) -echo "memory_limit = 512M" | sudo tee -a /etc/php/8.4/cli/php.ini - -# Verify -php -i | grep memory_limit -``` - -**Option B: Use Environment Variable** -```bash -# Set in .env -echo "COMPOSER_MEMORY_LIMIT=-1" >> .env - -# Or export globally -export COMPOSER_MEMORY_LIMIT=-1 -make install -``` - -### Issue 4: Lock File Out of Date - -**Symptoms:** -```bash -$ make install -Warning: The lock file is not up to date with the latest changes in composer.json -``` - -**Diagnosis:** -```bash -# Check what changed -composer validate --strict -``` - -**Solutions:** - -**Option A: Update Lock File** -```bash -# If intentional changes -make update - -# Commit new lock file -git add composer.lock -git commit -m "chore: update composer.lock" -``` - -**Option B: Restore composer.json** -```bash -# If accidental changes -git checkout composer.json -make install -``` - -### Issue 5: Authentication Required - -**Symptoms:** -```bash -$ make install -Authentication required (gitlab.com): -``` - -**Solutions:** - -**Option A: Add Auth Token** -```bash -# GitHub token -composer config --global github-oauth.github.com YOUR_TOKEN - -# GitLab token -composer config --global gitlab-oauth.gitlab.com YOUR_TOKEN - -# Verify -cat ~/.composer/auth.json -``` - -**Option B: SSH Keys** -```bash -# Use SSH instead of HTTPS -composer config --global use-github-api false - -# Ensure SSH key is added -ssh-add ~/.ssh/id_rsa -``` - -### Issue 6: Network Timeouts - -**Symptoms:** -```bash -$ make install - Failed to download symfony/http-kernel from dist: connection timed out -``` - -**Solutions:** - -**Option A: Increase Timeout** -```bash -# Increase process timeout -export COMPOSER_PROCESS_TIMEOUT=600 -make install -``` - -**Option B: Use Different Mirror** -```bash -# Configure packagist mirror -composer config --global repo.packagist composer https://packagist.com -``` - -**Option C: Retry** -```bash -# Sometimes transient network issues -make install # Try again -``` - ---- - -## Best Practices - -### 1. Version Control - -#### Commit These Files -```bash -✅ composer.json # Dependency definitions -✅ composer.lock # Locked versions -✅ Makefile # Build automation -✅ .make/ # Makefile modules -``` - -#### Ignore These Files -```bash -❌ vendor/ # Downloaded dependencies -❌ .phpunit.cache # Test cache -❌ .php-cs-fixer.cache # CS cache -❌ build/ # Build artifacts -❌ coverage/ # Coverage reports -``` - -**.gitignore Example:** -```gitignore -# Dependencies -/vendor/ - -# Build artifacts -/build/ -/coverage/ -/reports/ - -# Caches -/.phpunit.cache -/.phpunit.result.cache -/.php-cs-fixer.cache -/var/cache/ - -# Infection -infection.log -infection.html -``` - -### 2. Update Strategy - -#### Semantic Versioning Approach -```json -{ - "require": { - "kariricode/router": "^2.0", // Major: Breaking changes - "symfony/console": "~6.4.0", // Minor: New features - "psr/log": "3.0.*" // Patch: Bug fixes only - } -} -``` - -**Recommended Constraints:** -- `^2.0` - Allow minor and patch updates (2.0, 2.1, 2.1.1) -- `~2.1.0` - Allow patch updates only (2.1.0, 2.1.1, 2.1.2) -- `2.1.*` - Alias for `~2.1.0` -- `>=2.0 <3.0` - Explicit range - -#### Update Workflow -```bash -# Weekly: Check for updates -make outdated - -# Review release notes for each package -# https://github.com/vendor/package/releases - -# Update patch versions (safe) -make exec-php CMD="composer update --prefer-stable" - -# Test thoroughly -make ci-full - -# Commit if successful -git add composer.lock -git commit -m "chore: update dependencies (patch)" -``` - -### 3. Security Hygiene -```bash -# Daily (automated in CI) -make security - -# Weekly (manual review) -make outdated -make security-strict - -# Monthly (dependency audit) -make update -make ci-full -``` - -### 4. Environment Consistency - -#### Team Setup -```bash -# Document in README.md -## Requirements -- PHP 8.4+ -- Composer 2.x - -## Setup -make check-php -make install-dev -make info -``` - -#### CI/CD Setup -```yaml -# .github/workflows/ci.yml -- name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.4' - coverage: xdebug - -- name: Validate Environment - run: make check-php - -- name: Install Dependencies - run: make install - -- name: Verify Installation - run: make verify-install -``` - -### 5. Cleanup Routine -```bash -# Before commit -make clean - -# Weekly -make clean -make install - -# Monthly (or disk space low) -make clean-all -make fresh-install -``` - ---- - -## Command Reference - -### Prerequisites -```bash -make check-php # Validate PHP version (8.4+) -make debug-composer # Debug Composer configuration -make info # Show environment information -``` - -### Installation -```bash -make install # Standard installation from lock -make install-dev # Development installation -make fresh-install # Clean installation (regenerates lock) -make update # Update dependencies -make verify-install # Verify installation success -``` - -### Validation & Security -```bash -make validate # Validate composer.json -make security # Security vulnerability scan -make security-strict # Strict security audit -make outdated # Check outdated dependencies -``` - -### Cleanup -```bash -make clean # Remove build artifacts -make clean-all # Deep clean (includes vendor/) -``` - ---- - -## Quick Reference Card -``` -╔═══════════════════════════════════════════════════════════╗ -║ Setup & Installation Quick Reference ║ -╠═══════════════════════════════════════════════════════════╣ -║ CHECK PHP │ make check-php ║ -║ INSTALL │ make install ║ -║ FRESH START │ make fresh-install ║ -║ UPDATE │ make update ║ -║──────────────┼────────────────────────────────────────────║ -║ VERIFY │ make verify-install ║ -║ VALIDATE │ make validate ║ -║ SECURITY │ make security ║ -║ OUTDATED │ make outdated ║ -║──────────────┼────────────────────────────────────────────║ -║ CLEAN │ make clean ║ -║ DEEP CLEAN │ make clean-all ║ -║ INFO │ make info ║ -╚═══════════════════════════════════════════════════════════╝ - -Daily Workflow: - make install → make clean → make test - -Weekly Workflow: - make outdated → make security → make update - -Fresh Start: - make clean-all → make fresh-install → make verify-install -``` - ---- - -**Version**: 1.0.0 -**Module**: `Makefile.setup.mk` -**Maintainer**: Walmir Silva diff --git a/.docs/MAKEFILE.md b/.docs/MAKEFILE.md deleted file mode 100644 index 9bd59d9..0000000 --- a/.docs/MAKEFILE.md +++ /dev/null @@ -1,324 +0,0 @@ -
- -# KaririCode DevKit - -[![PHP Version](https://img.shields.io/badge/PHP-8.4%2B-777BB4?style=for-the-badge&logo=php)](https://www.php.net) -[![Docker](https://img.shields.io/badge/Docker-Ready-2496ED?style=for-the-badge&logo=docker)](https://www.docker.com) -[![Make](https://img.shields.io/badge/Make-Automation-6D00CC?style=for-the-badge&logo=gnu)](https://www.gnu.org/software/make/) -[![License](https://img.shields.io/badge/License-MIT-green?style=for-the-badge)](LICENSE) - -**Professional development environment for KaririCode Framework components** - -[Website](https://kariricode.org) | [Documentation](https://kariricode.org/docs) | [GitHub](https://github.com/KaririCode-Framework/kariricode-devkit) - -
- ---- - -## 📚 Documentation Index - -| Document | Scope | Makefile Modules | -|----------|-------|------------------| -| **[Setup & Installation](MAKEFILE-setup.md)** | Dependency management, environment setup | `Makefile.setup.mk` | -| **[Quality Assurance](MAKEFILE-qa.md)** | Testing, linting, static analysis | `Makefile.qa.mk` | -| **[CI/CD Pipelines](MAKEFILE-pipeline.md)** | Orchestrated workflows, pre-commit hooks | `Makefile.orchestration.mk` | -| **[Development Helpers](MAKEFILE-helpers.md)** | Benchmarks, git hooks, release management | `Makefile.helpers.mk` | -| **[Docker Commands](MAKEFILE-docker.md)** | Docker execution, isolated environments | `Makefile.docker-*.mk` | -| **[Docker Compose](MAKEFILE-compose.md)** | Full stack environment management | `Makefile.docker-compose.mk` | - ---- - -## 🚀 Quick Start - -### First Time Setup -```bash -# 1. Check prerequisites -make check-php # Verify PHP 8.4+ - -# 2. Install dependencies -make install # Production dependencies -make install-dev # + Development tools - -# 3. Verify installation -make info # Show environment info -``` - -### Daily Development Workflow -```bash -# Local development -make format # Auto-fix code style -make test # Run tests -make analyse # Static analysis - -# Docker environment -make up # Start services -make test-compose # Integration tests -make down # Stop services -``` - -### CI/CD Integration -```bash -# Fast CI (1-2 min) -make ci # Essential checks - -# Full CI (3-5 min) -make ci-full # Complete validation - -# Docker CI (isolated) -make docker-ci # Consistent environment -``` - ---- - -## 📖 Architecture Overview - -### Module Organization -``` -.make/ -├── core/ # 🔧 Shared infrastructure -│ ├── Makefile.variables.mk # Colors, paths, tools -│ └── Makefile.functions.mk # Reusable shell functions -│ -├── local/ # 💻 Local development -│ ├── Makefile.setup.mk # Install, update, clean -│ ├── Makefile.qa.mk # Test, lint, analyse -│ └── Makefile.helpers.mk # Bench, hooks, stats -│ -├── pipeline/ # 🔄 CI/CD workflows -│ └── Makefile.orchestration.mk # Composed pipelines -│ -└── docker/ # 🐳 Docker execution - ├── Makefile.docker-core.mk # Shell, composer, php - ├── Makefile.docker-compose.mk # Service lifecycle - ├── Makefile.docker-qa.mk # QA in containers - ├── Makefile.docker-image.mk # Image management - └── Makefile.docker-tools.mk # Utilities -``` - -### Design Principles - -**Single Responsibility Principle (SRP)** -- Each `.mk` file has one clear purpose -- Setup ≠ QA ≠ Docker ≠ Pipeline - -**DRY (Don't Repeat Yourself)** -- Shared logic in `core/Makefile.functions.mk` -- Variables centralized in `core/Makefile.variables.mk` - -**Composability** -- Complex workflows built from simple targets -- `make ci` = lint + analyse + test -- `make cd` = ci-full + bench + release prep - -**Flexibility** -- Local execution: `make test` -- Docker execution: `make docker-test` -- Same interface, different environment - ---- - -## 🎯 Command Categories - -### By Frequency - -**Every Commit** -```bash -make format # Auto-fix style -make lint # Syntax check -make test-unit # Fast tests -``` - -**Before Push** -```bash -make ci # Full local CI -make analyse # Deep static analysis -``` - -**Weekly** -```bash -make update # Update dependencies -make security # Security audit -make outdated # Check for updates -``` - -**Release** -```bash -make cd # Complete validation -make tag VERSION=X.Y.Z # Create tag -``` - -### By Environment - -**Local Machine** -- Fast iteration -- IDE integration -- Custom configuration - -**Docker Container** -- Isolated environment -- Consistent results -- CI/CD simulation - -**Docker Compose** -- Full stack (PHP + Redis + Memcached) -- Integration tests -- Service dependencies - ---- - -## 📋 Command Reference - -### Essential Commands -```bash -# Help -make help # Show all commands -make -help # Module-specific help - -# Setup -make install # Install dependencies -make clean # Clean artifacts -make verify-install # Verify setup - -# Quality -make test # Run tests -make analyse # Static analysis -make format # Auto-fix code - -# Pipelines -make ci # Fast CI -make ci-full # Complete CI -make pre-commit # Quick checks - -# Docker -make docker-ci # CI in Docker -make up # Start compose -make down # Stop compose -``` - -### Getting Help -```bash -# General help -make help - -# Module-specific -make bench-help # Benchmark parameters - -# Debug -make info # Environment info -make debug-composer # Composer config -make env-check # Docker env vars -``` - ---- - -## 🔍 Troubleshooting - -### Quick Diagnostics -```bash -# Check environment -make info # PHP, Composer, tools -make check-php # PHP version check - -# Verify installation -make verify-install # Check dependencies - -# Docker issues -make docker-info # Docker environment -make validate-compose # docker-compose.yml syntax -make logs # Service logs -``` - -### Common Issues - -| Issue | Quick Fix | Documentation | -|-------|-----------|---------------| -| Tests not executing | `export XDEBUG_MODE=off` | [MAKEFILE-qa.md](MAKEFILE-qa.md#troubleshooting) | -| Port conflicts | Edit `.env`, change `APP_PORT` | [MAKEFILE-compose.md](MAKEFILE-compose.md#port-conflicts) | -| Composer errors | `make debug-composer` | [MAKEFILE-setup.md](MAKEFILE-setup.md#troubleshooting) | -| PHPStan errors | `make phpstan-baseline` | [MAKEFILE-qa.md](MAKEFILE-qa.md#static-analysis) | - ---- - -## 🎓 Learning Path - -### Beginner (Day 1) - -1. Read: [Setup & Installation](MAKEFILE-setup.md) -2. Run: `make install && make info` -3. Test: `make test` - -### Intermediate (Week 1) - -1. Read: [Quality Assurance](MAKEFILE-qa.md) -2. Setup: `make git-hooks-setup` -3. Practice: `make pre-commit` workflow - -### Advanced (Month 1) - -1. Read: [Docker Compose](MAKEFILE-compose.md) -2. Setup: `make up` -3. Integrate: `make test-compose` - -### Expert (Month 3) - -1. Read: [CI/CD Pipelines](MAKEFILE-pipeline.md) -2. Customize: Add project-specific targets -3. Optimize: Benchmark and tune - ---- - -## 🤝 Contributing - -### Adding New Targets - -1. **Choose the right module** - - Setup related? → `Makefile.setup.mk` - - Testing related? → `Makefile.qa.mk` - - Docker related? → `Makefile.docker-*.mk` - -2. **Follow conventions** -```makefile - target-name: ## Description for help - @echo "$(BLUE)→ Action...$(RESET)" - # implementation - @echo "$(GREEN)✓ Success$(RESET)" -``` - -3. **Document in corresponding .md file** - -4. **Test locally and in Docker** -```bash - make target-name - make docker- # if applicable -``` - -### Documentation Standards - -- Use **semantic organization** -- Include **working examples** -- Add **troubleshooting sections** -- Maintain **consistent formatting** -- Update **command reference tables** - ---- - -## 📞 Support - -### Resources - -- **Issues**: Found a bug? [Open an issue](https://github.com/KaririCode-Framework/kariricode-devkit/issues) -- **Discussions**: Questions? [Start a discussion](https://github.com/KaririCode-Framework/kariricode-devkit/discussions) -- **Documentation**: [Full documentation index](#-documentation-index) - -### Quick Links - -- [Prerequisites](MAKEFILE-setup.md#prerequisites) -- [Installation Guide](MAKEFILE-setup.md#installation) -- [Command Reference](#-command-reference) -- [Troubleshooting](#-troubleshooting) -- [Best Practices](MAKEFILE-pipeline.md#best-practices) - ---- - -**Version**: 1.0.0 -**Maintainer**: Walmir Silva diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 651cc00..0000000 --- a/.editorconfig +++ /dev/null @@ -1,37 +0,0 @@ -# EditorConfig - Preserves premium comment formatting -# https://editorconfig.org - -root = true - -[*] -charset = utf-8 -end_of_line = lf -insert_final_newline = true -trim_trailing_whitespace = true -indent_style = space -indent_size = 4 - -[*.php] -indent_style = space -indent_size = 4 -max_line_length = 120 - -# Preserves spaces in comments (no trim) -[*.php] -trim_trailing_whitespace = false - -[*.{yml,yaml}] -indent_size = 2 - -[*.{json,json5}] -indent_size = 2 - -[*.{md,markdown}] -trim_trailing_whitespace = false -max_line_length = off - -[composer.json] -indent_size = 4 - -[{package.json,*.yml,*.yaml}] -indent_size = 2 diff --git a/.env.example b/.env.example deleted file mode 100644 index 3e85f1e..0000000 --- a/.env.example +++ /dev/null @@ -1,212 +0,0 @@ -# ============================================================================== -# KaririCode DevKit - Environment Configuration -# ============================================================================== -# Professional environment variables for Docker Compose -# Copy to .env and customize: cp .env.example .env -# ============================================================================== - -# ============================================================================== -# APPLICATION -# ============================================================================== -APP_NAME=kariricode-devkit -APP_ENV=development -APP_DEBUG=true -APP_SECRET=change-me-in-production -APP_VERSION=dev -SYMFONY_ENV=dev - -# ============================================================================== -# DOCKER & SYSTEM -# ============================================================================== -# User/Group IDs (match host user for volume permissions) -# Note: PHP container runs as root internally but processes run as www-data -UID=1000 -GID=1000 - -# Timezone -TZ=UTC - -# Volume consistency (macOS performance) -VOLUME_CONSISTENCY=cached - -# ============================================================================== -# PORTS -# ============================================================================== -APP_PORT=8089 -REDIS_PORT=6379 -MEMCACHED_PORT=11211 - -# ============================================================================== -# PHP SERVICE (kariricode/php-api-stack) -# ============================================================================== -PHP_STACK_VERSION=dev - -# PHP Configuration -PHP_MEMORY_LIMIT=2G -PHP_MAX_EXECUTION_TIME=300 -PHP_UPLOAD_MAX_FILESIZE=50M -PHP_POST_MAX_SIZE=50M - -# Resource Limits -PHP_CPU_LIMIT=2.0 -PHP_CPU_RESERVATION=0.5 -PHP_MEMORY_RESERVATION=512M - -# OPcache -OPCACHE_ENABLE=1 -OPCACHE_VALIDATE_TIMESTAMPS=1 -OPCACHE_REVALIDATE_FREQ=2 - -# PHP-FPM Configuration -PHP_FPM_PM=dynamic -PHP_FPM_PM_MAX_CHILDREN=50 -PHP_FPM_PM_START_SERVERS=5 -PHP_FPM_PM_MIN_SPARE_SERVERS=5 -PHP_FPM_PM_MAX_SPARE_SERVERS=10 -PHP_FPM_PM_MAX_REQUESTS=500 - -# ============================================================================== -# XDEBUG -# ============================================================================== -XDEBUG_MODE=off -XDEBUG_CLIENT_HOST=host.docker.internal -XDEBUG_CLIENT_PORT=9003 -XDEBUG_SESSION=PHPSTORM - -# ============================================================================== -# SESSION HANDLER -# ============================================================================== -# Session storage: files (local) or redis (distributed) -SESSION_SAVE_HANDLER=files -SESSION_SAVE_PATH=/tmp - -# ============================================================================== -# REDIS (Internal - Inside PHP Container) -# ============================================================================== -# Redis runs internally at 127.0.0.1:6379 inside PHP container -REDIS_HOST=127.0.0.1 -REDIS_PORT_INTERNAL=6379 -REDIS_PASSWORD= -REDIS_DB=0 -REDIS_TIMEOUT=5 -REDIS_LOG_FILE=/var/log/redis.log - -# ============================================================================== -# NGINX (Internal - Inside PHP Container) -# ============================================================================== -NGINX_WORKER_PROCESSES=auto -NGINX_WORKER_CONNECTIONS=1024 -NGINX_CLIENT_MAX_BODY_SIZE=100M -NGINX_KEEPALIVE_TIMEOUT=65 - -# ============================================================================== -# COMPOSER -# ============================================================================== -COMPOSER_MEMORY_LIMIT=-1 -COMPOSER_HOME=/root/.composer - -# ============================================================================== -# MEMCACHED (External Service) -# ============================================================================== -MEMCACHED_VERSION=1.6-alpine -MEMCACHED_MEMORY=256 -MEMCACHED_MAX_CONNECTIONS=1024 -MEMCACHED_THREADS=4 -MEMCACHED_MAX_ITEM_SIZE=5m - -# Resource Limits -MEMCACHED_CPU_LIMIT=1.0 -MEMCACHED_CPU_RESERVATION=0.25 -MEMCACHED_MEMORY_TOTAL=512M -MEMCACHED_MEMORY_RESERVATION=256M - -# Health Check -MEMCACHED_HEALTHCHECK_INTERVAL=10s -MEMCACHED_HEALTHCHECK_TIMEOUT=5s -MEMCACHED_HEALTHCHECK_RETRIES=3 -MEMCACHED_HEALTHCHECK_START_PERIOD=10s - -# ============================================================================== -# FEATURES -# ============================================================================== -DEMO_MODE=false -HEALTH_CHECK_INSTALL=false - -# ============================================================================== -# NETWORK -# ============================================================================== -ENABLE_IPV6=false -BRIDGE_NAME=kariricode0 -NETWORK_MTU=1500 -NETWORK_SUBNET=172.20.0.0/16 -NETWORK_GATEWAY=172.20.0.1 - -# ============================================================================== -# LOGGING -# ============================================================================== -LOG_MAX_SIZE=10m -LOG_MAX_FILE=3 -LOG_LEVEL=info - -# ============================================================================== -# HEALTH CHECKS -# ============================================================================== -HEALTHCHECK_INTERVAL=30s -HEALTHCHECK_TIMEOUT=10s -HEALTHCHECK_RETRIES=3 -HEALTHCHECK_START_PERIOD=40s - -# ============================================================================== -# TEMPORARY FILESYSTEM -# ============================================================================== -TMPFS_SIZE=100M - -# ============================================================================== -# TROUBLESHOOTING -# ============================================================================== -# Port conflicts? -# - Change APP_PORT, REDIS_PORT, or MEMCACHED_PORT -# - Run: make diagnose-ports -# - Run: make fix-ports -# -# Performance issues on macOS? -# - Try VOLUME_CONSISTENCY=delegated -# -# Permission errors? -# - Note: Container runs as root, but PHP-FPM/Nginx run as www-data -# - Check file ownership: ls -la -# -# Memory issues? -# - Adjust PHP_MEMORY_LIMIT and resource limits -# -# Session/Redis errors? -# - Use SESSION_SAVE_HANDLER=files for development -# - Use SESSION_SAVE_HANDLER=redis for distributed sessions -# -# PHP-FPM crashes? -# - Check logs: make logs SERVICE=php -# - Verify config: docker compose config -# -# Run diagnostics: -# - make env-check # Validate .env file -# - make diagnose-ports # Check port conflicts -# - make docker-info # Docker environment info -# - make status # Service status -# - make health # Health checks -# ============================================================================== - -# ============================================================================== -# PRODUCTION NOTES -# ============================================================================== -# For production deployment: -# 1. Change APP_ENV=production -# 2. Set APP_DEBUG=false -# 3. Generate strong APP_SECRET: openssl rand -hex 32 -# 4. Set XDEBUG_MODE=off -# 5. Set OPCACHE_VALIDATE_TIMESTAMPS=0 -# 6. Use SESSION_SAVE_HANDLER=redis with password -# 7. Set proper resource limits -# 8. Enable HTTPS/SSL termination -# 9. Use managed Redis/Memcached services -# 10. Implement proper backup strategy -# ============================================================================== diff --git a/.env.xdebug b/.env.xdebug deleted file mode 100644 index 1351c2a..0000000 --- a/.env.xdebug +++ /dev/null @@ -1 +0,0 @@ -XDEBUG_MODE=debug,coverage diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 753b72c..0000000 --- a/.gitattributes +++ /dev/null @@ -1,23 +0,0 @@ -* text=auto eol=lf - -*.php text eol=lf -*.json text eol=lf -*.xml text eol=lf -*.yml text eol=lf -*.neon text eol=lf - -*.md text eol=lf -*.txt text eol=lf - -.gitattributes export-ignore -.gitignore export-ignore -.php-cs-fixer.php export-ignore -phpstan.neon export-ignore -psalm.xml export-ignore -phpunit.xml export-ignore -infection.json export-ignore -phpbench.json export-ignore -/.github export-ignore -/tests export-ignore -/benchmarks export-ignore -/docs export-ignore \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ebb5cec..4e23927 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,280 +1,68 @@ -name: CI Pipeline +name: CI on: - push: - branches: - - main - - develop - - feature/* - pull_request: - branches: - - main - - develop - schedule: - # Run daily at 2 AM UTC - - cron: "0 2 * * *" + push: + branches: [main, develop] + pull_request: + branches: [main, develop] jobs: - # ============================================================================ - # TEST JOB - Run tests across multiple PHP versions - # ============================================================================ - test: - name: Tests (PHP ${{ matrix.php }}) - runs-on: ubuntu-latest - permissions: - # Required for codecov/codecov-action to get an OIDC token. - id-token: write - # Required for actions/checkout to fetch code. - contents: read + quality: + name: Quality Pipeline + runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - php: - - "8.4" - - "8.3" - include: - - php: "8.4" - coverage: true + steps: + - uses: actions/checkout@v4 - services: - redis: - image: redis:7-alpine - ports: - - 6379:6379 - options: >- - --health-cmd "redis-cli ping" - --health-interval 10s - --health-timeout 5s - --health-retries 5 + - uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + coverage: pcov - memcached: - image: memcached:1.6-alpine - ports: - - 11211:11211 + - name: Install dependencies + run: composer install --no-interaction --prefer-dist --optimize-autoloader - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 + - name: Initialize devkit + run: vendor/bin/kcode init - - name: Setup PHP ${{ matrix.php }} - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - extensions: redis, memcached, mbstring, xml, ctype, json, tokenizer, opcache - coverage: xdebug - tools: composer:v2 - env: - COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Code style check + run: vendor/bin/kcode cs:fix --check - - name: Get Composer cache directory - id: composer-cache - run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + - name: Static analysis + run: vendor/bin/kcode analyse - - name: Cache Composer dependencies - uses: actions/cache@v4 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: | - ${{ runner.os }}-composer- + - name: Tests + run: vendor/bin/kcode test --coverage - - name: Validate composer.json and composer.lock - run: composer validate --strict --no-check-lock + build-phar: + name: PHAR Build Smoke Test + runs-on: ubuntu-latest + needs: quality - - name: Install dependencies - run: composer install --prefer-dist --no-progress --no-interaction + steps: + - uses: actions/checkout@v4 - - name: Check PHP syntax - run: find src tests -name "*.php" -print0 | xargs -0 -n1 php -l + - uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + extensions: phar, zlib + ini-values: phar.readonly=0 - - name: Run tests - if: matrix.coverage != true - run: vendor/bin/phpunit --no-coverage - env: - REDIS_HOST: localhost - REDIS_PORT: 6379 - MEMCACHED_HOST: localhost - MEMCACHED_PORT: 11211 + - name: Install humbug/box + run: | + wget -q -O box https://github.com/box-project/box/releases/latest/download/box.phar + chmod +x box + sudo mv box /usr/local/bin/box - - name: Run tests with coverage - if: matrix.coverage == true - run: vendor/bin/phpunit --coverage-clover=coverage.xml --coverage-text - env: - XDEBUG_MODE: coverage - REDIS_HOST: localhost - REDIS_PORT: 6379 - MEMCACHED_HOST: localhost - MEMCACHED_PORT: 11211 + - name: Install dependencies + run: composer install --no-interaction --prefer-dist --optimize-autoloader - - name: Upload coverage to Codecov - if: matrix.coverage == true - uses: codecov/codecov-action@v4 - with: - files: ./coverage.xml - flags: unittests - name: codecov-php-${{ matrix.php }} - fail_ci_if_error: false + - name: Compile PHAR + run: php -d phar.readonly=0 box compile --config=box.json - - name: Archive code coverage results - if: matrix.coverage == true - uses: actions/upload-artifact@v4 - with: - name: code-coverage-report - path: coverage.xml - retention-days: 30 - - # ============================================================================ - # CODE QUALITY JOB - Run all quality checks - # ============================================================================ - code-quality: - name: Code Quality - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: "8.4" - extensions: mbstring, xml, ctype, json, tokenizer - coverage: none - tools: composer:v2, cs2pr - env: - COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Get Composer cache directory - id: composer-cache - run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - - - name: Cache Composer dependencies - uses: actions/cache@v4 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: | - ${{ runner.os }}-composer- - - - name: Install dependencies - run: composer install --prefer-dist --no-progress --no-interaction - - - name: Check code style (PHP CS Fixer) - run: vendor/bin/php-cs-fixer fix --dry-run --diff --format=checkstyle | cs2pr - continue-on-error: true - - - name: Run static analysis (PHPStan) - run: vendor/bin/phpstan analyse --error-format=github --no-progress - - - name: Run mess detector (PHPMD) - run: vendor/bin/phpmd src github devkit/.config/phpmd/ruleset.xml - continue-on-error: true - - - name: Check for security vulnerabilities - run: composer audit --format=plain - - # ============================================================================ - # MUTATION TESTING JOB (Optional - runs on schedule) - # ============================================================================ - mutation: - name: Mutation Testing - runs-on: ubuntu-latest - if: github.event_name == 'schedule' || contains(github.event.head_commit.message, '[mutation]') - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: "8.4" - extensions: mbstring, xml, ctype, json - coverage: xdebug - tools: composer:v2 - - - name: Install dependencies - run: composer install --prefer-dist --no-progress - - - name: Run Infection (Mutation Testing) - run: | - composer require --dev infection/infection - vendor/bin/infection --threads=4 --min-msi=70 --min-covered-msi=80 - continue-on-error: true - - # ============================================================================ - # DOCUMENTATION JOB - # ============================================================================ - documentation: - name: Documentation - runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: "8.4" - tools: composer:v2 - - - name: Install dependencies - run: composer install --prefer-dist --no-progress - - - name: Generate API documentation - run: | - composer require --dev phpdocumentor/phpdocumentor - vendor/bin/phpdoc -d src -t docs/api - continue-on-error: true - - - name: Deploy documentation to GitHub Pages - uses: peaceiris/actions-gh-pages@v4 - if: success() - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./docs/api - publish_branch: gh-pages - - # ============================================================================ - # COMPATIBILITY CHECK JOB - # ============================================================================ - compatibility: - name: Backward Compatibility Check - runs-on: ubuntu-latest - if: github.event_name == 'pull_request' - - steps: - - name: Checkout current code - uses: actions/checkout@v4 - with: - path: current - - - name: Checkout base code - uses: actions/checkout@v4 - with: - ref: ${{ github.base_ref }} - path: base - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: "8.4" - tools: composer:v2 - - - name: Install roave/backward-compatibility-check - run: | - cd current - composer require --dev roave/backward-compatibility-check - - - name: Check backward compatibility - run: | - cd current - vendor/bin/roave-backward-compatibility-check --from=../base - continue-on-error: true + - name: Verify PHAR + run: | + php build/kcode.phar --version + php build/kcode.phar --help + php build/kcode.phar init + echo "✓ PHAR smoke test passed" diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 459e034..b4f0464 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -5,7 +5,7 @@ on: branches: - main - develop - - feature/* + - 'feature/**' pull_request: branches: - main @@ -14,69 +14,46 @@ on: jobs: # ============================================================================ - # PHP CS FIXER - Code Style Check + # DEPENDENCY VALIDATION # ============================================================================ - php-cs-fixer: - name: PHP CS Fixer + dependencies: + name: Dependency Validation runs-on: ubuntu-latest steps: - - name: Checkout code - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - name: Setup PHP - uses: shivammathur/setup-php@v2 + - uses: shivammathur/setup-php@v2 with: - php-version: "8.4" - extensions: mbstring, xml + php-version: '8.4' + tools: composer:v2 coverage: none - tools: composer:v2, cs2pr - - - name: Get Composer cache directory - id: composer-cache - run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - - - name: Cache dependencies - uses: actions/cache@v4 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- - - name: Install dependencies - run: composer install --prefer-dist --no-progress - - - name: Run PHP CS Fixer - run: | - vendor/bin/php-cs-fixer fix --dry-run --diff --format=checkstyle | cs2pr + - name: Validate composer.json + run: composer validate --strict --no-check-lock - - name: Annotate with PHP CS Fixer results - if: failure() - run: | - echo "::error::Code style issues found. Run 'make cs-fix' to fix them." + - name: Check platform requirements + run: composer check-platform-reqs --no-dev # ============================================================================ - # PHPSTAN - Static Analysis + # SECURITY AUDIT # ============================================================================ - phpstan: - name: PHPStan (Level Max) + security: + name: Security Audit runs-on: ubuntu-latest steps: - - name: Checkout code - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - name: Setup PHP - uses: shivammathur/setup-php@v2 + - uses: shivammathur/setup-php@v2 with: - php-version: "8.4" - extensions: mbstring, xml, ctype, json - coverage: none + php-version: '8.4' tools: composer:v2 + coverage: none - name: Get Composer cache directory id: composer-cache - run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" - name: Cache dependencies uses: actions/cache@v4 @@ -85,41 +62,31 @@ jobs: key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- - - name: Install dependencies - run: composer install --prefer-dist --no-progress - - - name: Run PHPStan - run: vendor/bin/phpstan analyse --error-format=github --no-progress + - name: Install dependencies (no-dev for audit) + run: composer install --no-dev --prefer-dist --no-progress - - name: Generate PHPStan baseline (if needed) - if: failure() - run: | - vendor/bin/phpstan analyse --generate-baseline - echo "::warning::PHPStan baseline generated. Consider fixing issues instead of ignoring them." - continue-on-error: true + - name: Run composer audit + run: composer audit --format=plain # ============================================================================ - # PHPMD - Mess Detector + # STATIC ANALYSIS (PHPStan) # ============================================================================ - phpmd: - name: PHP Mess Detector + phpstan: + name: PHPStan Static Analysis runs-on: ubuntu-latest steps: - - name: Checkout code - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - name: Setup PHP - uses: shivammathur/setup-php@v2 + - uses: shivammathur/setup-php@v2 with: - php-version: "8.4" - extensions: mbstring, xml + php-version: '8.4' coverage: none tools: composer:v2 - name: Get Composer cache directory id: composer-cache - run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" - name: Cache dependencies uses: actions/cache@v4 @@ -131,247 +98,76 @@ jobs: - name: Install dependencies run: composer install --prefer-dist --no-progress - - name: Run PHPMD - run: vendor/bin/phpmd src github devkit/.config/phpmd/ruleset.xml - continue-on-error: true - - - name: Upload PHPMD results - if: always() - uses: actions/upload-artifact@v4 - with: - name: phpmd-results - path: phpmd-report.xml - continue-on-error: true - - # ============================================================================ - # RECTOR - Automated Refactoring Check - # ============================================================================ - rector: - name: Rector Dry Run - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: "8.4" - extensions: mbstring, xml, ctype, json - coverage: none - tools: composer:v2 - - - name: Install dependencies - run: composer install --prefer-dist --no-progress - - - name: Run Rector (dry-run) - run: vendor/bin/rector process --dry-run --no-progress-bar - continue-on-error: true - - - name: Suggest improvements - if: failure() - run: | - echo "::warning::Rector found potential improvements. Run 'make rector-fix' to apply them." - - # ============================================================================ - # SECURITY AUDIT - Composer Security Check - # ============================================================================ - security: - name: Security Audit - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: "8.4" - tools: composer:v2 - - - name: Install dependencies - run: composer install --prefer-dist --no-progress - - - name: Run security audit - run: composer audit --format=json > security-report.json - continue-on-error: true - - - name: Check for vulnerabilities - run: | - VULNS=$(jq '.advisories | length' security-report.json) - if [ "$VULNS" -gt 0 ]; then - echo "::error::Found $VULNS security vulnerabilities" - cat security-report.json - exit 1 - fi - echo "::notice::No security vulnerabilities found" + - name: Initialize devkit config + run: vendor/bin/kcode init - - name: Upload security report - if: always() - uses: actions/upload-artifact@v4 - with: - name: security-report - path: security-report.json + - name: Run PHPStan via kcode + run: vendor/bin/kcode analyse # ============================================================================ - # PSALM - Static Analysis (Alternative) + # CODE STYLE (PHP CS Fixer via kcode) # ============================================================================ - psalm: - name: Psalm Static Analysis + cs-fixer: + name: Code Style Check runs-on: ubuntu-latest - if: contains(github.event.head_commit.message, '[psalm]') steps: - - name: Checkout code - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - name: Setup PHP - uses: shivammathur/setup-php@v2 + - uses: shivammathur/setup-php@v2 with: - php-version: "8.4" - extensions: mbstring, xml + php-version: '8.4' coverage: none tools: composer:v2 - - name: Install dependencies - run: | - composer install --prefer-dist --no-progress - composer require --dev vimeo/psalm - - - name: Run Psalm - run: vendor/bin/psalm --output-format=github --no-progress - continue-on-error: true - - # ============================================================================ - # CODE METRICS - PHPMetrics - # ============================================================================ - metrics: - name: Code Metrics - runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' || contains(github.event.head_commit.message, '[metrics]') - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: "8.4" - tools: composer:v2 - - - name: Install dependencies - run: | - composer install --prefer-dist --no-progress - composer require --dev phpmetrics/phpmetrics - - - name: Generate metrics - run: vendor/bin/phpmetrics --report-html=metrics src - - - name: Upload metrics - uses: actions/upload-artifact@v4 - with: - name: code-metrics - path: metrics/ - retention-days: 30 - - # ============================================================================ - # DEAD CODE DETECTION - # ============================================================================ - dead-code: - name: Dead Code Detection - runs-on: ubuntu-latest - if: github.event_name == 'pull_request' - - steps: - - name: Checkout code - uses: actions/checkout@v4 + - name: Get Composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT" - - name: Setup PHP - uses: shivammathur/setup-php@v2 + - name: Cache dependencies + uses: actions/cache@v4 with: - php-version: "8.4" - tools: composer:v2 + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- - name: Install dependencies run: composer install --prefer-dist --no-progress - - name: Detect dead code (via PHPStan) - run: | - composer require --dev phpstan/phpstan-deprecation-rules - vendor/bin/phpstan analyse src --level=max - continue-on-error: true - - # ============================================================================ - # DEPENDENCY VALIDATION - # ============================================================================ - dependencies: - name: Dependency Validation - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 + - name: Initialize devkit config + run: vendor/bin/kcode init - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: "8.4" - tools: composer:v2 - - - name: Validate composer.json - run: composer validate --strict --no-check-lock - - - name: Check for outdated dependencies - run: composer outdated --direct --strict - continue-on-error: true - - - name: Check platform requirements - run: composer check-platform-reqs + - name: Check code style via kcode + run: vendor/bin/kcode cs:fix --check # ============================================================================ - # FINAL REPORT - Quality Summary + # QUALITY SUMMARY # ============================================================================ quality-summary: name: Quality Summary runs-on: ubuntu-latest - needs: [php-cs-fixer, phpstan, phpmd, security, dependencies] + needs: [dependencies, security, phpstan, cs-fixer] if: always() steps: - name: Check overall quality status run: | - echo "## Quality Checks Summary" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "| Check | Status |" >> $GITHUB_STEP_SUMMARY - echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY - echo "| PHP CS Fixer | ${{ needs.php-cs-fixer.result }} |" >> $GITHUB_STEP_SUMMARY - echo "| PHPStan | ${{ needs.phpstan.result }} |" >> $GITHUB_STEP_SUMMARY - echo "| PHPMD | ${{ needs.phpmd.result }} |" >> $GITHUB_STEP_SUMMARY - echo "| Security | ${{ needs.security.result }} |" >> $GITHUB_STEP_SUMMARY - echo "| Dependencies | ${{ needs.dependencies.result }} |" >> $GITHUB_STEP_SUMMARY - - if [ "${{ needs.php-cs-fixer.result }}" != "success" ] || \ + echo "## Quality Checks Summary" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "| Check | Status |" >> "$GITHUB_STEP_SUMMARY" + echo "|-------|--------|" >> "$GITHUB_STEP_SUMMARY" + echo "| Dependencies | ${{ needs.dependencies.result }} |" >> "$GITHUB_STEP_SUMMARY" + echo "| Security | ${{ needs.security.result }} |" >> "$GITHUB_STEP_SUMMARY" + echo "| PHPStan | ${{ needs.phpstan.result }} |" >> "$GITHUB_STEP_SUMMARY" + echo "| CS Fixer | ${{ needs.cs-fixer.result }} |" >> "$GITHUB_STEP_SUMMARY" + + if [ "${{ needs.security.result }}" != "success" ] || \ [ "${{ needs.phpstan.result }}" != "success" ] || \ - [ "${{ needs.security.result }}" != "success" ]; then - echo "" >> $GITHUB_STEP_SUMMARY - echo "❌ Quality checks failed. Please review the logs above." >> $GITHUB_STEP_SUMMARY + [ "${{ needs.cs-fixer.result }}" != "success" ]; then + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "❌ Quality checks failed." >> "$GITHUB_STEP_SUMMARY" exit 1 fi - echo "" >> $GITHUB_STEP_SUMMARY - echo "✅ All quality checks passed!" >> $GITHUB_STEP_SUMMARY - - - name: Comment on PR - if: github.event_name == 'pull_request' && failure() - uses: actions/github-script@v7 - with: - script: | - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: '⚠️ **Quality checks failed**\n\nPlease run `make qa` locally to fix issues before merging.' - }) + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "✅ All quality checks passed!" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..4ab1fae --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,87 @@ +name: Release + +on: + push: + tags: + - 'v*' + +permissions: + contents: write + +jobs: + build-phar: + name: Build kcode.phar + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + extensions: phar, zlib + ini-values: phar.readonly=0 + tools: composer:v2 + + - name: Install humbug/box + run: | + wget -q -O box https://github.com/box-project/box/releases/latest/download/box.phar + chmod +x box + sudo mv box /usr/local/bin/box + box --version + + - name: Install dependencies + run: composer install --no-interaction --prefer-dist --optimize-autoloader + + - name: Compile PHAR + run: | + php -d phar.readonly=0 box compile --config=box.json + ls -lh build/kcode.phar + + - name: Verify PHAR + run: | + php build/kcode.phar --version + php build/kcode.phar --help + echo "✓ PHAR verification passed" + + - name: Self-test + run: | + php build/kcode.phar init + php build/kcode.phar migrate --dry-run + echo "✓ Self-test passed" + + - name: Extract version from tag + id: version + run: echo "tag=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT" + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.version.outputs.tag }} + name: KaririCode Devkit ${{ steps.version.outputs.tag }} + draft: false + prerelease: false + files: build/kcode.phar + body: | + ## Installation + + ### PHAR (standalone) + ```bash + wget https://github.com/kariricode/devkit/releases/download/${{ steps.version.outputs.tag }}/kcode.phar + chmod +x kcode.phar + sudo mv kcode.phar /usr/local/bin/kcode + ``` + + ### Composer + ```bash + composer require --dev kariricode/devkit + ``` + + ## Quick Start + ```bash + kcode init + kcode migrate + kcode quality + ``` + + See [CHANGELOG.md](CHANGELOG.md) for details. diff --git a/.gitignore b/.gitignore index e1bf860..5043565 100644 --- a/.gitignore +++ b/.gitignore @@ -1,128 +1,48 @@ -# ============================================================================== -# KaririCode DevKit - .gitignore -# ============================================================================== -# Mantém o repositório limpo, rastreando apenas código-fonte e configs relevantes -# ============================================================================== - -# ------------------------------------------------------------------------------ -# Composer / Vendors -# ------------------------------------------------------------------------------ /vendor/ -composer.lock -/composer.phar - -# ------------------------------------------------------------------------------ -# Environment Files -# ------------------------------------------------------------------------------ -.env -.env.local -.env.*.local -/.envrc -/.envrc.d/ - -# ------------------------------------------------------------------------------ -# Build / Distribution / Packaging -# ------------------------------------------------------------------------------ /build/ -/dist/ -/tmp/ -/var/tmp/ -/*.phar +/.kcode/ -# ------------------------------------------------------------------------------ -# QA & Code Quality Tools -# ------------------------------------------------------------------------------ # PHPUnit .phpunit.cache/ .phpunit.result.cache /coverage/ /reports/ -# PHPStan -/.phpstan.cache -/var/cache/phpstan/ - -# Psalm -/.psalm/ -/psalm-baseline.xml - -# PHP-CS-Fixer -/.php-cs-fixer.cache - -# Infection (mutation testing) -/var/cache/infection/ -/infection*.{log,html,md} -/infection-debug.log -/infection-summary.log - -# PHPBench -/.phpbench/ -/benchmarks/ +# CS-Fixer +.php-cs-fixer.cache -# Rector / PHPMD / CodeSniffer -/.rector.php -/.phpcs-cache -/.phpmd-cache -/.phpstan-baseline.neon -/.rector-cache +# PHAR artefacts +*.phar -# ------------------------------------------------------------------------------ -# Logs -# ------------------------------------------------------------------------------ -/*.log -/logs/ -/storage/logs/ -/var/log/ -/*.pid +# composer.lock (library — callers pin versions) +composer.lock -# ------------------------------------------------------------------------------ -# Docker / WSL / CI -# ------------------------------------------------------------------------------ -/.docker/ -/docker-compose.override.yml -/.wslconfig -/.wsl2/ -.cache/ -.dockerignore +# Environment files +.env +.env.*.local -# ------------------------------------------------------------------------------ -# IDEs / Editors / OS -# ------------------------------------------------------------------------------ -# JetBrains / VSCode -/.idea/ +# IDE /.vscode/ +/.idea/ *.code-workspace -# Vim / Emacs / Misc -*~ -*.swp -*.swo - -# macOS / Windows +# Docker/legacy scaffold (not part of this library) +docker-compose.yml +docker-compose.override.yml +.env.example +.env.xdebug + +# Misc legacy config +.gitattributes +.editorconfig +infection.json +phpbench.json +phpcs.xml +phpmd.xml +phpstan.neon +.php-cs-fixer.php +.rector.php + +# OS .DS_Store Thumbs.db -Desktop.ini - -# ------------------------------------------------------------------------------ -# Node / Frontend (quando integrados ao DevKit) -# ------------------------------------------------------------------------------ -node_modules/ -npm-debug.log* -yarn-debug.log* -yarn-error.log* -.pnpm-debug.log* - -# ------------------------------------------------------------------------------ -# Backups / Artifacts -# ------------------------------------------------------------------------------ -*.bak -*.old -*.orig -*.rej -*.tmp - -# ------------------------------------------------------------------------------ -# Cache geral -# ------------------------------------------------------------------------------ -.cache/ -var/cache/ diff --git a/.make/core/Makefile.functions.mk b/.make/core/Makefile.functions.mk deleted file mode 100644 index 4dcac01..0000000 --- a/.make/core/Makefile.functions.mk +++ /dev/null @@ -1,66 +0,0 @@ -# ============================================================================== -# KaririCode\DevKit - Reusable Functions -# ============================================================================== -# Define funções shell reutilizáveis seguindo DRY principle -# ============================================================================== - -# --- Validação de Parâmetros --- -define validate_param - @if [ -z "$($(1))" ]; then \ - echo -e "$(RED)✗ $(1) required. Usage: $(2)$(RESET)"; \ - exit 1; \ - fi -endef - -# --- Execução Docker Genérica --- -define docker_exec - @echo -e "$(BLUE)→ Running $(1) in Docker...$(RESET)" - @$(DOCKER_RUN) $(DOCKER_IMAGE) $(2) - @echo -e "$(GREEN)✓ $(1) complete$(RESET)" -endef - -define docker_exec_make - @echo -e "$(BLUE)→ Running make $(1) in Docker...$(RESET)" - @$(DOCKER_RUN) $(DOCKER_IMAGE) make $(1) - @echo -e "$(GREEN)✓ Docker make $(1) complete$(RESET)" -endef - -# --- Verificação de Arquivo --- -define check_file - @test -f $(1) || (echo "$(RED)✗ $(2) not found$(RESET)" && exit 1) -endef - -# --- Criação de Diretório --- -define ensure_dir - @mkdir -p $(1) -endef - -# --- Header de Pipeline --- -define pipeline_header - @echo -e "$(BOLD)$(CYAN)╔════════════════════════════════════════════════════════╗$(RESET)" - @echo -e "$(BOLD)$(CYAN)║ $(1)$(RESET)" - @echo -e "$(BOLD)$(CYAN)╚════════════════════════════════════════════════════════╝$(RESET)" - @echo -e "" -endef - -# --- Verificação de Branch Git --- -define check_git_branch - @CURRENT_BRANCH=$$(git rev-parse --abbrev-ref HEAD); \ - if [ "$$CURRENT_BRANCH" != "$(1)" ]; then \ - echo -e "$(RED)✗ This action requires branch '$(1)' (current: $$CURRENT_BRANCH)$(RESET)"; \ - exit 1; \ - fi -endef - -# --- Verificação de Versão PHP --- -define check_php_version - @echo -e "$(BLUE)→ Checking PHP version...$(RESET)"; \ - CURRENT_VERSION="$(PHP_VERSION)"; \ - MIN_VERSION="$(PHP_MIN_VERSION)"; \ - LOWEST=$$(printf '%s\n%s' "$$MIN_VERSION" "$$CURRENT_VERSION" | sort -V | head -n1); \ - if [ "$$LOWEST" != "$$MIN_VERSION" ]; then \ - echo -e "$(RED)✗ PHP $$MIN_VERSION+ required, found $$CURRENT_VERSION$(RESET)"; \ - exit 1; \ - fi; \ - echo -e "$(GREEN)✓ PHP version $$CURRENT_VERSION OK (>= $$MIN_VERSION)$(RESET)" -endef diff --git a/.make/core/Makefile.variables.mk b/.make/core/Makefile.variables.mk deleted file mode 100644 index 8fa271c..0000000 --- a/.make/core/Makefile.variables.mk +++ /dev/null @@ -1,58 +0,0 @@ -# ============================================================================== -# KaririCode\DevKit - Core Variables & Configuration -# ============================================================================== -# Centraliza todas as variáveis compartilhadas entre módulos -# ============================================================================== - -# --- Cores & Formatação --- -RESET := \033[0m -BOLD := \033[1m -RED := \033[31m -GREEN := \033[32m -YELLOW := \033[33m -BLUE := \033[34m -MAGENTA := \033[35m -CYAN := \033[36m - -# --- Docker Configuration --- -DOCKER_IMAGE := kariricode/php-api-stack:dev -DOCKER_RUN := docker run --rm -v $(PWD):/var/www/html -w /var/www/html -DOCKER_RUN_IT := docker run --rm -it -v $(PWD):/var/www/html -w /var/www/html - -# --- PHP Configuration --- -PHP := $(shell which php) -PHP_VERSION := $(shell $(PHP) -r 'echo PHP_VERSION;') -PHP_MIN_VERSION := 8.4.0 -PHP_CLEAN_RUN := $(PHP) -d xdebug.mode=off -d pcov.enabled=0 -d opcache.enable=1 - -# --- Composer Configuration --- -# Prevent the 'COMPOSER' make variable from conflicting with the -# 'COMPOSER' environment variable used by the tool itself. -unexport COMPOSER -COMPOSER_BIN := $(shell command -v composer 2>/dev/null || echo "") -# Use the full path for execution. Fallback to 'composer' if not found. -COMPOSER := $(if $(COMPOSER_BIN),$(COMPOSER_BIN),composer) - -# --- Directories --- -SRC_DIR := src -TEST_DIR := tests -BENCHMARK_DIR := benchmarks -BUILD_DIR := build -COVERAGE_DIR := coverage -REPORTS_DIR := reports -CACHE_DIR := var/cache -BENCH_REPORT_DIR := $(BUILD_DIR)/benchmarks - -# --- Vendor Binaries --- -VENDOR_BIN := vendor/bin -PHPUNIT := $(VENDOR_BIN)/phpunit -PHPSTAN := $(VENDOR_BIN)/phpstan -PSALM := $(VENDOR_BIN)/psalm -PHPCS := $(VENDOR_BIN)/phpcs -PHPCBF := $(VENDOR_BIN)/phpcbf -PHP_CS_FIXER := $(VENDOR_BIN)/php-cs-fixer -INFECTION := $(VENDOR_BIN)/infection -PHPBENCH := $(VENDOR_BIN)/phpbench - -# --- Export all for subshells --- -export diff --git a/.make/docker/Makefile.docker-compose.mk b/.make/docker/Makefile.docker-compose.mk deleted file mode 100644 index 45b4aa7..0000000 --- a/.make/docker/Makefile.docker-compose.mk +++ /dev/null @@ -1,431 +0,0 @@ -# ============================================================================== -# KaririCode\DevKit - Docker Compose Management -# ============================================================================== -# Gerencia o ciclo de vida completo do ambiente Docker Compose -# ============================================================================== - -.PHONY: up down restart stop start status logs logs-follow ps \ - exec-php exec-memcached health config validate-compose \ - up-build rebuild prune network-inspect \ - check-ports diagnose-ports fix-ports kill-port port-scan up-safe \ - clean-docker check-docker-ports - -# Force bash for interactive commands -SHELL := /bin/bash - -# ============================================================================== -# DOCKER CLEANUP (executa antes das verificações de porta) -# ============================================================================== - -clean-docker: ## Clean orphaned Docker containers and networks - @echo -e "$(BLUE)→ Cleaning orphaned Docker resources...$(RESET)" - @echo -e "$(YELLOW) Removing stopped containers...$(RESET)" - @docker compose down 2>/dev/null || true - @docker container prune -f 2>/dev/null || true - @echo -e "$(YELLOW) Removing unused networks...$(RESET)" - @docker network prune -f 2>/dev/null || true - @echo -e "$(GREEN)✓ Docker cleanup complete$(RESET)" - -check-docker-ports: ## Check Docker containers using required ports - @echo -e "$(BLUE)→ Checking Docker containers for port conflicts...$(RESET)" - @bash -c ' \ - if [ -f .env ]; then \ - source .env 2>/dev/null || true; \ - fi; \ - APP_PORT=$${APP_PORT:-8089}; \ - REDIS_PORT=$${REDIS_PORT:-6379}; \ - MEMCACHED_PORT=$${MEMCACHED_PORT:-11211}; \ - CONFLICTS=0; \ - echo ""; \ - echo -e "$(CYAN)Checking all Docker containers (running and stopped):$(RESET)"; \ - for port in $$APP_PORT $$REDIS_PORT $$MEMCACHED_PORT; do \ - CONTAINERS=$$(docker ps -a --format "{{.Names}}\t{{.Ports}}" 2>/dev/null | grep ":$$port" || true); \ - if [ -n "$$CONTAINERS" ]; then \ - echo ""; \ - echo -e "$(RED)✗ Port $$port is bound to Docker containers:$(RESET)"; \ - echo "$$CONTAINERS" | while read line; do echo " $$line"; done; \ - CONFLICTS=$$((CONFLICTS + 1)); \ - else \ - echo -e "$(GREEN)✓ Port $$port not bound to Docker containers$(RESET)"; \ - fi; \ - done; \ - if [ $$CONFLICTS -gt 0 ]; then \ - echo ""; \ - echo -e "$(YELLOW)Run: make clean-docker$(RESET) to remove orphaned containers"; \ - exit 1; \ - fi \ - ' - -# ============================================================================== -# PORT CONFLICT DETECTION & RESOLUTION -# ============================================================================== - -check-ports: clean-docker check-docker-ports ## Check for port conflicts (includes Docker cleanup) - @echo -e "$(BLUE)→ Checking system ports for conflicts...$(RESET)" - @bash -c ' \ - if [ -f .env ]; then \ - source .env 2>/dev/null || true; \ - fi; \ - CONFLICTS=0; \ - PORTS="$${APP_PORT:-8089} $${REDIS_PORT:-6379} $${MEMCACHED_PORT:-11211}"; \ - for port in $$PORTS; do \ - if lsof -Pi :$$port -sTCP:LISTEN -t >/dev/null 2>&1; then \ - PROCESS=$$(lsof -Pi :$$port -sTCP:LISTEN -t | head -1); \ - CMD=$$(ps -p $$PROCESS -o comm= 2>/dev/null || echo "unknown"); \ - echo -e "$(RED)✗ Port $$port in use by system process PID $$PROCESS ($$CMD)$(RESET)"; \ - CONFLICTS=$$((CONFLICTS + 1)); \ - elif ss -ltn | grep -q ":$$port "; then \ - echo -e "$(RED)✗ Port $$port in use (detected by ss)$(RESET)"; \ - CONFLICTS=$$((CONFLICTS + 1)); \ - else \ - echo -e "$(GREEN)✓ Port $$port is available$(RESET)"; \ - fi; \ - done; \ - if [ $$CONFLICTS -gt 0 ]; then \ - echo ""; \ - echo -e "$(YELLOW)Resolution options:$(RESET)"; \ - echo -e " 1. Run: $(CYAN)make diagnose-ports$(RESET) for detailed info"; \ - echo -e " 2. Run: $(CYAN)make fix-ports$(RESET) to auto-resolve"; \ - echo -e " 3. Run: $(CYAN)make kill-port PORT=$(RESET) for specific port"; \ - exit 1; \ - fi; \ - echo -e "$(GREEN)✓ All ports available$(RESET)" \ - ' - -diagnose-ports: ## Detailed port conflict diagnosis - @echo -e "$(BOLD)$(CYAN)Port Conflict Diagnosis$(RESET)" - @echo -e "$(BLUE)╔════════════════════════════════════════════════════════╗$(RESET)" - @echo -e "" - @bash -c ' \ - if [ -f .env ]; then \ - source .env 2>/dev/null || true; \ - fi; \ - APP_PORT=$${APP_PORT:-8089}; \ - REDIS_PORT=$${REDIS_PORT:-6379}; \ - MEMCACHED_PORT=$${MEMCACHED_PORT:-11211}; \ - echo -e "$(CYAN)Required Ports:$(RESET)"; \ - echo " APP_PORT: $$APP_PORT"; \ - echo " REDIS_PORT: $$REDIS_PORT"; \ - echo " MEMCACHED_PORT: $$MEMCACHED_PORT"; \ - echo ""; \ - echo -e "$(CYAN)System Port Status:$(RESET)"; \ - for port in $$APP_PORT $$REDIS_PORT $$MEMCACHED_PORT; do \ - echo ""; \ - echo -e "$(YELLOW)Port $$port:$(RESET)"; \ - if lsof -Pi :$$port -sTCP:LISTEN -t >/dev/null 2>&1; then \ - lsof -Pi :$$port -sTCP:LISTEN | awk "NR>1 {printf \" PID: %s | Command: %s | User: %s\n\", \$$2, \$$1, \$$3}"; \ - echo -e " $(RED)Status: IN USE (lsof)$(RESET)"; \ - elif ss -ltn | grep -q ":$$port "; then \ - echo -e " $(RED)Status: IN USE (ss)$(RESET)"; \ - ss -ltnp | grep ":$$port " | awk "{print \" \" \$$0}"; \ - else \ - echo -e " $(GREEN)Status: AVAILABLE$(RESET)"; \ - fi; \ - done; \ - echo ""; \ - echo -e "$(CYAN)Docker Containers (All):$(RESET)"; \ - docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" 2>/dev/null | grep -E "$$APP_PORT|$$REDIS_PORT|$$MEMCACHED_PORT" || echo " None found"; \ - echo ""; \ - echo -e "$(BLUE)╚════════════════════════════════════════════════════════╝$(RESET)"; \ - echo ""; \ - echo -e "$(CYAN)Suggested Actions:$(RESET)"; \ - echo -e " 1. $(YELLOW)make clean-docker$(RESET) - Remove orphaned Docker containers"; \ - echo -e " 2. $(YELLOW)make fix-ports$(RESET) - Auto-fix all conflicts"; \ - echo -e " 3. $(YELLOW)make kill-port PORT=$(RESET) - Kill specific process"; \ - echo " 4. Edit .env to change ports"; \ - ' - -fix-ports: ## Automatically fix port conflicts (interactive) - @echo -e "$(YELLOW)⚠ This will attempt to free up conflicting ports$(RESET)" - @echo -e "$(YELLOW) Docker containers and processes will be terminated$(RESET)" - @echo -e "" - @bash -c ' \ - read -p "Continue? [y/N] " -n 1 -r; \ - echo; \ - if [[ $$REPLY =~ ^[Yy]$$ ]]; then \ - $(MAKE) --no-print-directory clean-docker; \ - $(MAKE) --no-print-directory _do_fix_ports; \ - else \ - echo -e "$(YELLOW)Cancelled$(RESET)"; \ - fi \ - ' - -_do_fix_ports: - @echo -e "$(BLUE)→ Scanning and fixing system port conflicts...$(RESET)" - @bash -c ' \ - if [ -f .env ]; then \ - source .env 2>/dev/null || true; \ - fi; \ - PORTS="$${APP_PORT:-8089} $${REDIS_PORT:-6379} $${MEMCACHED_PORT:-11211}"; \ - for port in $$PORTS; do \ - if lsof -Pi :$$port -sTCP:LISTEN -t >/dev/null 2>&1; then \ - PROCESS=$$(lsof -Pi :$$port -sTCP:LISTEN -t | head -1); \ - CMD=$$(ps -p $$PROCESS -o comm= 2>/dev/null || echo "unknown"); \ - echo -e "$(YELLOW)→ Terminating process $$PROCESS ($$CMD) on port $$port...$(RESET)"; \ - kill -15 $$PROCESS 2>/dev/null && sleep 1; \ - if lsof -Pi :$$port -sTCP:LISTEN -t >/dev/null 2>&1; then \ - echo -e "$(RED) Process didnt stop gracefully, forcing...$(RESET)"; \ - kill -9 $$PROCESS 2>/dev/null || sudo kill -9 $$PROCESS 2>/dev/null || true; \ - fi; \ - if ! lsof -Pi :$$port -sTCP:LISTEN -t >/dev/null 2>&1; then \ - echo -e "$(GREEN)✓ Port $$port freed$(RESET)"; \ - else \ - echo -e "$(RED)✗ Failed to free port $$port$(RESET)"; \ - fi; \ - fi; \ - done \ - ' - @echo -e "" - @$(MAKE) --no-print-directory check-ports - -kill-port: ## Kill process using specific port (usage: make kill-port PORT=11211) - @if [ -z "$(PORT)" ]; then \ - echo -e "$(RED)✗ PORT parameter required$(RESET)"; \ - echo -e "$(YELLOW)Usage: make kill-port PORT=11211$(RESET)"; \ - exit 1; \ - fi - @echo -e "$(BLUE)→ Checking port $(PORT)...$(RESET)" - @bash -c ' \ - if lsof -Pi :$(PORT) -sTCP:LISTEN -t >/dev/null 2>&1; then \ - PROCESS=$$(lsof -Pi :$(PORT) -sTCP:LISTEN -t | head -1); \ - CMD=$$(ps -p $$PROCESS -o comm= 2>/dev/null || echo "unknown"); \ - echo -e "$(YELLOW)Found process: PID $$PROCESS ($$CMD)$(RESET)"; \ - echo -e "$(YELLOW)Attempting graceful shutdown...$(RESET)"; \ - kill -15 $$PROCESS 2>/dev/null && sleep 2; \ - if lsof -Pi :$(PORT) -sTCP:LISTEN -t >/dev/null 2>&1; then \ - echo -e "$(RED)Process didnt stop, forcing shutdown...$(RESET)"; \ - kill -9 $$PROCESS 2>/dev/null || sudo kill -9 $$PROCESS 2>/dev/null || true; \ - fi; \ - if ! lsof -Pi :$(PORT) -sTCP:LISTEN -t >/dev/null 2>&1; then \ - echo -e "$(GREEN)✓ Port $(PORT) is now free$(RESET)"; \ - else \ - echo -e "$(RED)✗ Failed to free port $(PORT)$(RESET)"; \ - fi; \ - else \ - echo -e "$(GREEN)✓ Port $(PORT) is already free$(RESET)"; \ - fi \ - ' - -port-scan: ## Scan common ports for conflicts - @echo -e "$(BOLD)$(CYAN)Port Scanner$(RESET)" - @echo -e "$(BLUE)╔════════════════════════════════════════════════════════╗$(RESET)" - @echo -e "" - @for port in 80 443 3000 3306 5432 6379 8000 8080 8089 9000 11211 27017; do \ - if lsof -Pi :$$port -sTCP:LISTEN -t >/dev/null 2>&1; then \ - PROCESS=$$(lsof -Pi :$$port -sTCP:LISTEN -t | head -1); \ - CMD=$$(ps -p $$PROCESS -o comm= 2>/dev/null || echo "unknown"); \ - echo -e "$(RED)✗ Port $$port: IN USE$(RESET) (PID $$PROCESS - $$CMD)"; \ - else \ - echo -e "$(GREEN)✓ Port $$port: Available$(RESET)"; \ - fi; \ - done - @echo -e "" - @echo -e "$(BLUE)╚════════════════════════════════════════════════════════╝$(RESET)" - -# ============================================================================== -# LIFECYCLE MANAGEMENT -# ============================================================================== - -up-safe: check-ports up ## Start services after checking ports (recommended) - -up: ## Start Docker Compose services - @echo -e "$(BLUE)→ Starting Docker Compose services...$(RESET)" - @if [ ! -f .env ]; then \ - echo -e "$(YELLOW)⚠ .env not found, copying from .env.example...$(RESET)"; \ - cp .env.example .env 2>/dev/null || echo "$(RED)✗ .env.example not found$(RESET)"; \ - fi - @docker compose --profile development up -d || { \ - echo ""; \ - echo -e "$(RED)✗ Failed to start services$(RESET)"; \ - echo -e "$(YELLOW)Possible port conflict with orphaned containers$(RESET)"; \ - echo -e "$(YELLOW)Run 'make diagnose-ports' for analysis$(RESET)"; \ - echo -e "$(YELLOW)Run 'make clean-docker' to remove orphaned containers$(RESET)"; \ - exit 1; \ - } - @echo -e "$(GREEN)✓ Services started$(RESET)" - @sleep 2 - @$(MAKE) --no-print-directory status - -up-build: ## Start services with build - @echo -e "$(BLUE)→ Building and starting Docker Compose services...$(RESET)" - @docker compose --profile development up -d --build || { \ - echo ""; \ - echo -e "$(RED)✗ Failed to build/start services$(RESET)"; \ - echo -e "$(YELLOW)Run 'make clean-docker' to clean orphaned containers$(RESET)"; \ - exit 1; \ - } - @echo -e "$(GREEN)✓ Services built and started$(RESET)" - @$(MAKE) --no-print-directory status - -down: ## Stop and remove Docker Compose services - @echo -e "$(BLUE)→ Stopping Docker Compose services...$(RESET)" - @docker compose down -v --remove-orphans - @echo -e "$(GREEN)✓ Services stopped and removed$(RESET)" - -stop: ## Stop Docker Compose services (without removing) - @echo -e "$(BLUE)→ Stopping Docker Compose services...$(RESET)" - @docker compose stop - @echo -e "$(GREEN)✓ Services stopped$(RESET)" - -start: ## Start existing Docker Compose services - @echo -e "$(BLUE)→ Starting existing Docker Compose services...$(RESET)" - @docker compose start - @echo -e "$(GREEN)✓ Services started$(RESET)" - -restart: ## Restart Docker Compose services - @echo -e "$(BLUE)→ Restarting Docker Compose services...$(RESET)" - @docker compose restart - @echo -e "$(GREEN)✓ Services restarted$(RESET)" - -rebuild: down clean-volumes up-build ## Rebuild environment from scratch - @echo -e "$(GREEN)✓ Environment rebuilt$(RESET)" - -# ============================================================================== -# MONITORING & INSPECTION -# ============================================================================== - -status: ## Show services status - @echo -e "$(BOLD)$(CYAN)Docker Compose Services Status$(RESET)" - @echo -e "$(BLUE)╔════════════════════════════════════════════════════════╗$(RESET)" - @docker compose ps - @echo -e "$(BLUE)╚════════════════════════════════════════════════════════╝$(RESET)" - -ps: status ## Alias for status - -logs: ## Show logs from all services (usage: make logs SERVICE=php) - @if [ -n "$(SERVICE)" ]; then \ - echo -e "$(BLUE)→ Showing logs for service: $(SERVICE)$(RESET)"; \ - docker compose logs $(SERVICE); \ - else \ - echo -e "$(BLUE)→ Showing logs for all services$(RESET)"; \ - docker compose logs; \ - fi - -logs-follow: ## Follow logs from services (usage: make logs-follow SERVICE=php) - @if [ -n "$(SERVICE)" ]; then \ - echo -e "$(BLUE)→ Following logs for service: $(SERVICE)$(RESET)"; \ - docker compose logs -f $(SERVICE); \ - else \ - echo -e "$(BLUE)→ Following logs for all services$(RESET)"; \ - docker compose logs -f; \ - fi - -health: ## Check services health status - @echo -e "$(BOLD)$(CYAN)Services Health Check$(RESET)" - @echo -e "$(BLUE)╔════════════════════════════════════════════════════════╗$(RESET)" - @docker compose ps --format json 2>/dev/null | jq -r '.[] | "\(.Name): \(.Health // "N/A") - \(.State)"' 2>/dev/null || docker compose ps - @echo -e "$(BLUE)╚════════════════════════════════════════════════════════╝$(RESET)" - -# ============================================================================== -# CONTAINER INTERACTION -# ============================================================================== - -exec-php: ## Execute command in PHP container (usage: make exec-php CMD="php -v") - @if [ -z "$(CMD)" ]; then \ - echo -e "$(BLUE)→ Opening interactive shell in PHP container...$(RESET)"; \ - docker compose exec php /bin/bash; \ - else \ - echo -e "$(BLUE)→ Executing: $(CMD)$(RESET)"; \ - docker compose exec php $(CMD); \ - fi - -exec-memcached: ## Execute command in Memcached container - @echo -e "$(BLUE)→ Connecting to Memcached container...$(RESET)" - @docker compose exec memcached sh - -# ============================================================================== -# CONFIGURATION & VALIDATION -# ============================================================================== - -config: ## Validate and view Docker Compose configuration - @echo -e "$(BLUE)→ Validating Docker Compose configuration...$(RESET)" - @docker compose config - @echo -e "$(GREEN)✓ Configuration is valid$(RESET)" - -validate-compose: ## Validate docker-compose.yml syntax - @echo -e "$(BLUE)→ Validating docker-compose.yml...$(RESET)" - @docker compose config --quiet && \ - echo -e "$(GREEN)✓ docker-compose.yml is valid$(RESET)" || \ - (echo -e "$(RED)✗ docker-compose.yml has errors$(RESET)" && exit 1) - -# ============================================================================== -# NETWORK & RESOURCES -# ============================================================================== - -network-inspect: ## Inspect Docker network - @echo -e "$(BOLD)$(CYAN)Docker Network Information$(RESET)" - @echo -e "$(BLUE)╔════════════════════════════════════════════════════════╗$(RESET)" - @docker network inspect $$(docker compose config --format json | jq -r '.networks | keys[0]') 2>/dev/null || \ - echo -e "$(YELLOW)⚠ Network not found or not created yet$(RESET)" - @echo -e "$(BLUE)╚════════════════════════════════════════════════════════╝$(RESET)" - -prune: ## Remove unused Docker resources - @echo -e "$(YELLOW)⚠ This will remove all unused containers, networks, and volumes$(RESET)" - @echo -e "$(BLUE)→ Pruning Docker resources...$(RESET)" - @docker system prune -f --volumes - @echo -e "$(GREEN)✓ Docker resources pruned$(RESET)" - -clean-volumes: ## Remove all volumes (WARNING: data loss) - @echo -e "$(RED)⚠ WARNING: This will delete ALL volume data!$(RESET)" - @bash -c ' \ - read -p "Are you sure? [y/N] " -n 1 -r; \ - echo; \ - if [[ $$REPLY =~ ^[Yy]$$ ]]; then \ - echo -e "$(BLUE)→ Removing all volumes...$(RESET)"; \ - docker compose down -v; \ - echo -e "$(GREEN)✓ Volumes removed$(RESET)"; \ - else \ - echo -e "$(YELLOW)Cancelled$(RESET)"; \ - fi \ - ' - -# ============================================================================== -# QUICK ACTIONS -# ============================================================================== - -shell: exec-php ## Alias for exec-php (open shell) - -composer-install: ## Run composer install in PHP container - @echo -e "$(BLUE)→ Running composer install...$(RESET)" - @docker compose exec php composer install --no-interaction --prefer-dist --optimize-autoloader - @echo -e "$(GREEN)✓ Composer dependencies installed$(RESET)" - -composer-update: ## Run composer update in PHP container - @echo -e "$(BLUE)→ Running composer update...$(RESET)" - @docker compose exec php composer update --with-all-dependencies --no-interaction --prefer-dist --optimize-autoloader - @echo -e "$(GREEN)✓ Composer dependencies updated$(RESET)" - -test-compose: up ## Start services and run tests - @echo -e "$(BLUE)→ Waiting for services to be ready...$(RESET)" - @sleep 3 - @$(MAKE) --no-print-directory exec-php CMD="make test" - -# ============================================================================== -# DIAGNOSTIC & TROUBLESHOOTING -# ============================================================================== - -ports: ## Show exposed ports - @echo -e "$(BOLD)$(CYAN)Exposed Ports$(RESET)" - @echo -e "$(BLUE)╔════════════════════════════════════════════════════════╗$(RESET)" - @docker compose ps --format "table {{.Names}}\t{{.Ports}}" - @echo -e "$(BLUE)╚════════════════════════════════════════════════════════╝$(RESET)" - -inspect-php: ## Inspect PHP container - @echo -e "$(BOLD)$(CYAN)PHP Container Inspection$(RESET)" - @echo -e "$(BLUE)╔════════════════════════════════════════════════════════╗$(RESET)" - @docker compose exec php php -v - @echo -e "" - @docker compose exec php php -m - @echo -e "$(BLUE)╚════════════════════════════════════════════════════════╝$(RESET)" - -env-check: ## Verify environment variables - @echo -e "$(BOLD)$(CYAN)Environment Configuration$(RESET)" - @echo -e "$(BLUE)╔════════════════════════════════════════════════════════╗$(RESET)" - @if [ -f .env ]; then \ - echo -e "$(GREEN)✓ .env file exists$(RESET)"; \ - grep -E '^[A-Z_]+=' .env | head -20; \ - else \ - echo -e "$(RED)✗ .env file not found$(RESET)"; \ - echo -e "$(YELLOW) Run: cp .env.example .env$(RESET)"; \ - fi - @echo -e "$(BLUE)╚════════════════════════════════════════════════════════╝$(RESET)" diff --git a/.make/docker/Makefile.docker-core.mk b/.make/docker/Makefile.docker-core.mk deleted file mode 100644 index d7d8249..0000000 --- a/.make/docker/Makefile.docker-core.mk +++ /dev/null @@ -1,25 +0,0 @@ -# ============================================================================== -# KaririCode\DevKit - Docker Core Functions -# ============================================================================== -# Abstrai lógica compartilhada de execução Docker -# ============================================================================== - -.PHONY: docker-shell docker-composer docker-php - -# ============================================================================== -# CORE DOCKER ENTRYPOINTS -# ============================================================================== - -docker-shell: ## Open interactive shell in Docker - @echo -e "$(BLUE)→ Opening Docker shell ($(DOCKER_IMAGE))...$(RESET)" - @$(DOCKER_RUN_IT) $(DOCKER_IMAGE) /bin/bash - -docker-composer: ## Run composer in Docker (usage: make docker-composer CMD="install") - $(call validate_param,CMD,make docker-composer CMD='install') - @echo -e "$(BLUE)→ Running composer $(CMD) in Docker...$(RESET)" - @$(DOCKER_RUN) $(DOCKER_IMAGE) composer $(CMD) - -docker-php: ## Run PHP command in Docker (usage: make docker-php CMD="-v") - $(call validate_param,CMD,make docker-php CMD='-v') - @echo -e "$(BLUE)→ Running php $(CMD) in Docker...$(RESET)" - @$(DOCKER_RUN) $(DOCKER_IMAGE) php $(CMD) diff --git a/.make/docker/Makefile.docker-image.mk b/.make/docker/Makefile.docker-image.mk deleted file mode 100644 index c52b34a..0000000 --- a/.make/docker/Makefile.docker-image.mk +++ /dev/null @@ -1,41 +0,0 @@ -# ============================================================================== -# KaririCode\DevKit - Docker Image Management -# ============================================================================== -# -# Provides targets for managing the Docker image lifecycle, such as -# pulling, inspecting, and cleaning. -# -# Usage: -# make docker-pull -# make docker-info -# -# Author: Walmir Silva -# ============================================================================== - -# ============================================================================== -# DOCKER IMAGE TARGETS -# ============================================================================== - -.PHONY: docker-pull docker-info docker-clean - -docker-pull: ## Pull Docker image from registry - @echo -e "$(BLUE)→ Pulling Docker image $(DOCKER_IMAGE)...$(RESET)" - @docker pull $(DOCKER_IMAGE) - @echo -e "$(GREEN)✓ Docker image pulled$(RESET)" - -docker-info: ## Show Docker environment info - @echo -e "$(BOLD)$(CYAN)Docker Environment Information$(RESET)" - @echo -e "$(BLUE)═══════════════════════════════════════════════════════════$(RESET)" - @echo -e "Docker Image: $(DOCKER_IMAGE)" - @echo -e "Mount Point: $(PWD):/app" - @echo -e "" - @echo -e "$(BOLD)$(CYAN)Container PHP Info$(RESET)" - @echo -e "$(BLUE)═══════════════════════════════════════════════════════════$(RESET)" - @$(DOCKER_RUN) $(DOCKER_IMAGE) php -v - @echo -e "" - @$(DOCKER_RUN) $(DOCKER_IMAGE) composer --version - -docker-clean: ## Clean Docker resources - @echo -e "$(BLUE)→ Cleaning Docker resources...$(RESET)" - @docker system prune -f - @echo -e "$(GREEN)✓ Docker cleanup complete$(RESET)" diff --git a/.make/docker/Makefile.docker-qa.mk b/.make/docker/Makefile.docker-qa.mk deleted file mode 100644 index b4e99a0..0000000 --- a/.make/docker/Makefile.docker-qa.mk +++ /dev/null @@ -1,64 +0,0 @@ -# ============================================================================== -# KaririCode\DevKit - Docker CI/QA Pipeline -# ============================================================================== -# Pipeline completo de CI/QA em ambiente Docker isolado -# ============================================================================== - -.PHONY: docker-test docker-test-unit docker-test-integration docker-coverage \ - docker-analyse docker-phpstan docker-psalm docker-cs-check \ - docker-format docker-lint docker-ci docker-ci-full docker-bench - -# ============================================================================== -# DOCKER QA TARGETS (Individual) -# ============================================================================== - -docker-test: ## Run tests in Docker - $(call docker_exec_make,test) - -docker-test-unit: ## Run unit tests in Docker - $(call docker_exec_make,test-unit) - -docker-test-integration: ## Run integration tests in Docker - $(call docker_exec_make,test-integration) - -docker-coverage: ## Generate coverage in Docker - @echo -e "$(BLUE)→ Generating coverage in Docker...$(RESET)" - @$(DOCKER_RUN) $(DOCKER_IMAGE) make coverage - @echo -e "$(GREEN)✓ Coverage report: $(COVERAGE_DIR)/html/index.html$(RESET)" - -docker-analyse: ## Run static analysis in Docker - $(call docker_exec_make,analyse) - -docker-phpstan: ## Run PHPStan in Docker - $(call docker_exec_make,phpstan) - -docker-psalm: ## Run Psalm in Docker - $(call docker_exec_make,psalm) - -docker-cs-check: ## Check coding standards in Docker - $(call docker_exec_make,cs-check) - -docker-format: ## Format code in Docker - $(call docker_exec_make,format) - -docker-lint: ## Lint PHP files in Docker - $(call docker_exec_make,lint) - -# ============================================================================== -# DOCKER CI PIPELINES (Orchestration) -# ============================================================================== - -docker-ci: ## Run CI pipeline in Docker - $(call pipeline_header,"Docker CI Pipeline Isolated Environment") - $(call docker_exec_make,ci) - @echo -e "" - @echo -e "$(BOLD)$(GREEN)✓ Docker CI pipeline completed$(RESET)" - -docker-ci-full: ## Run full CI pipeline in Docker - $(call pipeline_header,"Docker Full CI Pipeline Isolated Environment") - $(call docker_exec_make,ci-full) - @echo -e "" - @echo -e "$(BOLD)$(GREEN)✓ Docker full CI pipeline completed$(RESET)" - -docker-bench: ## Run benchmarks in Docker - $(call docker_exec_make,bench) diff --git a/.make/docker/Makefile.docker-tools.mk b/.make/docker/Makefile.docker-tools.mk deleted file mode 100644 index 3e71c97..0000000 --- a/.make/docker/Makefile.docker-tools.mk +++ /dev/null @@ -1,71 +0,0 @@ -# ============================================================================== -# KaririCode\DevKit - Docker Development Tools -# ============================================================================== -# Ferramentas de desenvolvimento em ambiente Docker -# ============================================================================== - -.PHONY: docker-htop docker-vim docker-less docker-jq docker-yq \ - docker-lsof docker-strace docker-ip docker-nc - -# ============================================================================== -# INTERACTIVE TOOLS -# ============================================================================== - -docker-htop: ## Run htop utility in Docker - @echo -e "$(BLUE)→ Running htop in Docker...$(RESET)" - @$(DOCKER_RUN_IT) $(DOCKER_IMAGE) htop - -# ============================================================================== -# TEXT EDITORS & VIEWERS -# ============================================================================== - -docker-vim: ## Edit files with vim in Docker (usage: make docker-vim CMD="src/file.php") - $(call validate_param,CMD,make docker-vim CMD='src/file.php') - @echo -e "$(BLUE)→ Running vim $(CMD) in Docker...$(RESET)" - @$(DOCKER_RUN_IT) $(DOCKER_IMAGE) vim $(CMD) - -docker-less: ## View files with less in Docker (usage: make docker-less CMD="README.md") - $(call validate_param,CMD,make docker-less CMD='README.md') - @echo -e "$(BLUE)→ Running less $(CMD) in Docker...$(RESET)" - @$(DOCKER_RUN_IT) $(DOCKER_IMAGE) less $(CMD) - -# ============================================================================== -# DATA PROCESSING TOOLS -# ============================================================================== - -docker-jq: ## Run jq utility in Docker (usage: make docker-jq CMD="'.version' composer.json") - $(call validate_param,CMD,make docker-jq CMD="'.version' composer.json") - @echo -e "$(BLUE)→ Running jq $(CMD) in Docker...$(RESET)" - @$(DOCKER_RUN) $(DOCKER_IMAGE) sh -c "jq $(CMD)" - -docker-yq: ## Run yq utility in Docker (usage: make docker-yq CMD="'.services' docker-compose.yml") - $(call validate_param,CMD,make docker-yq CMD="'.services' docker-compose.yml") - @echo -e "$(BLUE)→ Running yq $(CMD) in Docker...$(RESET)" - @$(DOCKER_RUN) $(DOCKER_IMAGE) sh -c "yq $(CMD)" - -# ============================================================================== -# SYSTEM DIAGNOSTIC TOOLS -# ============================================================================== - -docker-lsof: ## Run lsof utility in Docker (usage: make docker-lsof CMD="-i") - @echo -e "$(BLUE)→ Running lsof $(CMD) in Docker...$(RESET)" - @$(DOCKER_RUN_IT) --cap-add=SYS_PTRACE --cap-add=SYS_ADMIN $(DOCKER_IMAGE) lsof $(CMD) - -docker-strace: ## Run strace utility in Docker (usage: make docker-strace CMD="php -v") - $(call validate_param,CMD,make docker-strace CMD='php -v') - @echo -e "$(BLUE)→ Running strace $(CMD) in Docker...$(RESET)" - @$(DOCKER_RUN_IT) --cap-add=SYS_PTRACE $(DOCKER_IMAGE) strace $(CMD) - -# ============================================================================== -# NETWORK DIAGNOSTIC TOOLS -# ============================================================================== - -docker-ip: ## Run iproute2 utility in Docker (usage: make docker-ip CMD="addr") - $(call validate_param,CMD,make docker-ip CMD='addr') - @echo -e "$(BLUE)→ Running ip $(CMD) in Docker...$(RESET)" - @$(DOCKER_RUN) $(DOCKER_IMAGE) ip $(CMD) - -docker-nc: ## Run netcat utility in Docker (usage: make docker-nc CMD="-vz localhost 9000") - $(call validate_param,CMD,make docker-nc CMD='-vz localhost 9000') - @echo -e "$(BLUE)→ Running netcat $(CMD) in Docker...$(RESET)" - @$(DOCKER_RUN_IT) $(DOCKER_IMAGE) nc $(CMD) diff --git a/.make/local/Makefile.helpers.mk b/.make/local/Makefile.helpers.mk deleted file mode 100644 index e77a686..0000000 --- a/.make/local/Makefile.helpers.mk +++ /dev/null @@ -1,221 +0,0 @@ -# ============================================================================== -# KaririCode\DevKit - Developer Helper Targets -# ============================================================================== - -.PHONY: bench bench-help git-hooks-setup git-hooks-remove \ - git-hooks-check watch-test server shell info tag release stats loc - -# ============================================================================== -# BENCHMARKING & PERFORMANCE (single target, param-driven) -# ============================================================================== - -# --- Tooling & Config --- -PHPBENCH ?= vendor/bin/phpbench -# This is the correct way: execute the phpbench script *with* a clean PHP binary. -PHP_CLEAN_RUN := $(PHP) -d xdebug.mode=off -d pcov.enabled=0 -d opcache.enable=1 - -# --- Directories --- -BENCHMARK_DIR ?= benchmarks -BENCH_REPORT_DIR ?= $(BUILD_DIR)/benchmarks - -# --- Default Parameters (Trimmed whitespace) --- -REF ?= auto -STORE ?= 0 -TAG ?= -ENFORCE_MAIN ?= 0 -REPORT ?= 0 - -# --- Encapsulated Benchmark Script --- -# This "encapsulates" the shell logic to hide command echoing -define BENCH_SCRIPT - @set -e; \ - BENCH_FLAGS="--progress=dots"; \ - \ - # ------------------ Comparison (REF) ------------------ - if [ "$(REF)" = "auto" ]; then \ - if $(PHP_CLEAN_RUN) $(PHPBENCH) log | grep -E -q "Tag:[[:space:]]+main"; then \ - printf "$(GREEN)✓ 'main' reference found. Enabling comparison…$(RESET)\n"; \ - BENCH_FLAGS="$$BENCH_FLAGS --ref=main"; \ - else \ - printf "$(YELLOW)⚠ No 'main' reference found. Running without comparison.$(RESET)\n"; \ - printf "$(YELLOW) Hint: make bench STORE=1 TAG=main ENFORCE_MAIN=1$(RESET)\n"; \ - fi; \ - elif [ -n "$(REF)" ]; then \ - printf "$(CYAN)→ Comparing against reference: %s$(RESET)\n" "$(REF)"; \ - BENCH_FLAGS="$$BENCH_FLAGS --ref=$(REF)"; \ - fi; \ - \ - # ------------------ Execution / Storage ------------------ - if [ "$(STORE)" = "1" ]; then \ - if [ -z "$(TAG)" ]; then \ - printf "$(RED)✗ TAG is required when STORE=1 (e.g., TAG=my-feature)$(RESET)\n"; \ - exit 1; \ - fi; \ - if [ "$(TAG)" = "main" ] && [ "$(ENFORCE_MAIN)" = "1" ]; then \ - CURRENT_BRANCH=$$(git rev-parse --abbrev-ref HEAD); \ - if [ "$$CURRENT_BRANCH" != "main" ]; then \ - printf "$(RED)✗ This action requires branch 'main' (current: %s)$(RESET)\n" "$$CURRENT_BRANCH"; \ - exit 1; \ - fi; \ - fi; \ - printf "$(BLUE)→ Storing run with tag '%s'…$(RESET)\n" "$(TAG)"; \ - if [ "$(REPORT)" = "1" ]; then \ - $(PHP_CLEAN_RUN) $(PHPBENCH) run $$BENCH_FLAGS --store --tag="$(TAG)" | tee "$(BENCH_REPORT_DIR)/last.txt"; \ - printf "$(GREEN)✓ Output saved to %s/last.txt$(RESET)\n" "$(BENCH_REPORT_DIR)"; \ - else \ - $(PHP_CLEAN_RUN) $(PHPBENCH) run $$BENCH_FLAGS --store --tag="$(TAG)"; \ - fi; \ - else \ - if [ "$(REPORT)" = "1" ]; then \ - $(PHP_CLEAN_RUN) $(PHPBENCH) run $$BENCH_FLAGS | tee "$(BENCH_REPORT_DIR)/last.txt"; \ - printf "$(GREEN)✓ Output saved to %s/last.txt$(RESET)\n" "$(BENCH_REPORT_DIR)"; \ - else \ - $(PHP_CLEAN_RUN) $(PHPBENCH) run $$BENCH_FLAGS; \ - fi; \ - fi; \ - \ - printf "$(GREEN)✓ Benchmarks complete$(RESET)\n" -endef - -bench: ## Run benchmarks (unified target). See 'make bench-help'. - @printf "$(BLUE)→ Running benchmarks (unified target)…$(RESET)\n" - @mkdir -p "$(BENCHMARK_DIR)" - @mkdir -p "$(BENCH_REPORT_DIR)" - @$(BENCH_SCRIPT) - -bench-help: ## Show usage help for the unified bench command - @echo -e "$(BOLD)$(CYAN)Benchmark Command Help$(RESET)" - @echo -e "$(BLUE)═══════════════════════════════════════════════════════════$(RESET)" - @echo -e "Single entrypoint: $(BOLD)make bench$(RESET)" - @echo -e "" - @echo -e "$(BOLD)Parameters:$(RESET)" - @echo -e " $(CYAN)REF=auto|main|$(RESET) Compare against a stored tag" - @echo -e " auto → try 'main' if available (default)" - @echo -e " main → compare against main reference" - @echo -e " → compare against any other tag" - @echo -e "" - @echo -e " $(CYAN)STORE=1 TAG=$(RESET) Store benchmarks with a given tag" - @echo -e " ex.: STORE=1 TAG=feature-x" - @echo -e "" - @echo -e " $(CYAN)ENFORCE_MAIN=1$(RESET) When TAG=main, ensures you are on branch 'main'" - @echo -e "" - @echo -e " $(CYAN)REPORT=1$(RESET) Save text output to $(BENCH_REPORT_DIR)/last.txt" - @echo -e "" - @echo -e "$(BOLD)Examples:$(RESET)" - @echo -e " make bench # Run normal benchmarks" - @echo -e " make bench REF=main # Compare against 'main'" - @echo -e " make bench REF=my-tag # Compare against custom tag" - @echo -e " make bench STORE=1 TAG=feat # Store results under tag 'feat'" - @echo -e " make bench STORE=1 TAG=main ENFORCE_MAIN=1 # Store results as 'main'" - @echo -e " make bench REF=main REPORT=1 # Compare and save output to last.txt" - @echo -e "" - @echo -e "$(GREEN)✓ Tip: Run 'make bench-help' anytime to see this guide$(RESET)" - -# ============================================================================== -# DEVELOPMENT HELPERS -# ============================================================================== -git-hooks-setup: ## Setup git hooks for development workflow - @echo -e "$(BLUE)→ Setting up git hooks...$(RESET)" - @mkdir -p .git/hooks - @if [ -f .git/hooks/pre-commit ] && [ ! -f .git/hooks/pre-commit.bak ]; then \ - echo -e "$(YELLOW)⚠ Existing pre-commit hook found. Backing up...$(RESET)"; \ - mv .git/hooks/pre-commit .git/hooks/pre-commit.bak; \ - fi - @echo '#!/bin/sh' > .git/hooks/pre-commit - @echo 'set -e' >> .git/hooks/pre-commit - @echo 'make pre-commit' >> .git/hooks/pre-commit - @chmod +x .git/hooks/pre-commit - @echo -e "$(GREEN)✓ Git hooks set up$(RESET)" - -git-hooks-remove: ## Remove git hooks and restore backups if any - @echo -e "$(BLUE)→ Cleaning up git hooks...$(RESET)" - @if [ -f .git/hooks/pre-commit.bak ]; then \ - echo -e "$(YELLOW)↩ Restoring backup pre-commit hook...$(RESET)"; \ - mv .git/hooks/pre-commit.bak .git/hooks/pre-commit; \ - elif [ -f .git/hooks/pre-commit ]; then \ - echo -e "$(RED)✗ Removing generated pre-commit hook...$(RESET)"; \ - rm .git/hooks/pre-commit; \ - else \ - echo -e "$(YELLOW)⚠ No pre-commit hook found$(RESET)"; \ - fi - @echo -e "$(GREEN)✓ Git hooks cleaned$(RESET)" - -git-hooks-check: ## Check if git hooks are installed correctly - @echo -e "$(BLUE)→ Verifying git hooks...$(RESET)" - @if [ -f .git/hooks/pre-commit ]; then \ - if grep -q "make pre-commit" .git/hooks/pre-commit; then \ - echo -e "$(GREEN)✓ pre-commit hook is installed correctly$(RESET)"; \ - else \ - echo -e "$(RED)✗ pre-commit hook exists but was not installed by this Makefile$(RESET)"; \ - fi \ - else \ - echo -e "$(RED)✗ pre-commit hook not found$(RESET)"; \ - fi - -info: ## Show PHP and project information - @echo -e "$(BOLD)$(CYAN)Project Information$(RESET)" - @echo -e "$(BLUE)═══════════════════════════════════════════════════════════$(RESET)" - @echo -e "PHP Version: $(PHP_VERSION)" - @echo -e "PHP Binary: $(PHP)" - @echo -e "Composer: $(COMPOSER)" - @echo -e "Project Directory: $(shell pwd)" - @echo -e "Source Directory: $(SRC_DIR)" - @echo -e "Test Directory: $(TEST_DIR)" - @echo -e "" - @echo -e "$(BOLD)$(CYAN)Installed Tools$(RESET)" - @echo -e "$(BLUE)═══════════════════════════════════════════════════════════$(RESET)" - @test -f $(PHPUNIT) && echo "PHPUnit: ✓" || echo "PHPUnit: ✗" - @test -f $(PHPSTAN) && echo "PHPStan: ✓" || echo "PHPStan: ✗" - @test -f $(PSALM) && echo "Psalm: ✓" || echo "Psalm: ✗" - @test -f $(PHPCS) && echo "PHPCS: ✓" || echo "PHPCS: ✗" - @test -f $(PHP_CS_FIXER) && echo "PHP-CS-Fixer: ✓" || echo "PHP-CS-Fixer: ✗" - @test -f $(INFECTION) && echo "Infection: ✓" || echo "Infection: ✗" - @test -f $(PHPBENCH) && echo "PHPBench: ✓" || echo "PHPBench: ✗" - -# ============================================================================== -# RELEASE MANAGEMENT -# ============================================================================== - -tag: ## Create a new git tag (usage: make tag VERSION=1.0.0) - @if [ -z "$(VERSION)" ]; then \ - echo -e "$(RED)✗ VERSION is required. Usage: make tag VERSION=1.0.0$(RESET)"; \ - exit 1; \ - fi - @echo -e "$(BLUE)→ Creating tag v$(VERSION)...$(RESET)" - @git tag -a "v$(VERSION)" -m "Release v$(VERSION)" - @git push origin "v$(VERSION)" - @echo -e "$(GREEN)✓ Tag v$(VERSION) created and pushed$(RESET)" - -release: cd ## Prepare release (run full CD pipeline) - @echo -e "$(BOLD)$(GREEN)✓ Release preparation complete$(RESET)" - @echo -e "" - @echo -e "$(CYAN)Next steps:$(RESET)" - @echo -e " 1. Update CHANGELOG.md" - @echo -e " 2. Update version in composer.json" - @echo -e " 3. Commit changes" - @echo -e " 4. Run: make tag VERSION=X.Y.Z" - @echo -e " 5. Push to GitHub" - @echo -e " 6. Create GitHub release" - -# ============================================================================== -# STATS & METRICS -# ============================================================================== - -stats: ## Show project statistics - @echo -e "$(BOLD)$(CYAN)Project Statistics$(RESET)" - @echo -e "$(BLUE)═══════════════════════════════════════════════════════════$(RESET)" - @echo -e "Total PHP files: $$(find $(SRC_DIR) -name '*.php' | wc -l)" - @echo -e "Total test files: $$(find $(TEST_DIR) -name '*.php' | wc -l)" - @echo -e "Lines of code: $$(find $(SRC_DIR) -name '*.php' -exec cat {} \; | wc -l)" - @echo -e "Lines of tests: $$(find $(TEST_DIR) -name '*.php' -exec cat {} \; | wc -l)" - @echo -e "" - @echo -e "$(BOLD)$(CYAN)Directory Sizes$(RESET)" - @echo -e "$(BLUE)═══════════════════════════════════════════════════════════$(RESET)" - @du -sh $(SRC_DIR) 2>/dev/null || true - @du -sh $(TEST_DIR) 2>/dev/null || true - @du -sh vendor 2>/dev/null || true - -loc: ## Count lines of code - @echo -e "$(BLUE)→ Counting lines of code...$(RESET)" - @find $(SRC_DIR) -name '*.php' -exec wc -l {} \; | awk '{sum += $$1} END {print "Source: " sum " lines"}' - @find $(TEST_DIR) -name '*.php' -exec wc -l {} \; | awk '{sum += $$1} END {print "Tests: " sum " lines"}' diff --git a/.make/local/Makefile.qa.mk b/.make/local/Makefile.qa.mk deleted file mode 100644 index 822cd14..0000000 --- a/.make/local/Makefile.qa.mk +++ /dev/null @@ -1,125 +0,0 @@ -# ============================================================================== -# KaririCode\DevKit - Quality Assurance Targets -# ============================================================================== - -.PHONY: test test-unit test-integration test-functional coverage coverage-text \ - mutation mutation-report analyse phpstan phpstan-baseline psalm \ - psalm-baseline psalm-taint cs-check format format-dry lint - -# ============================================================================== -# TESTING -# ============================================================================== - -test: ## Run PHPUnit tests - @echo -e "$(BLUE)→ Running tests...$(RESET)" - @$(PHPUNIT) --colors=always --testdox - @echo -e "$(GREEN)✓ Tests passed$(RESET)" - -test-unit: ## Run unit tests only - @echo -e "$(BLUE)→ Running unit tests...$(RESET)" - @$(PHPUNIT) --colors=always --testdox --testsuite=Unit - @echo -e "$(GREEN)✓ Unit tests passed$(RESET)" - -test-integration: ## Run integration tests only - @echo -e "$(BLUE)→ Running integration tests...$(RESET)" - @$(PHPUNIT) --colors=always --testdox --testsuite=Integration - @echo -e "$(GREEN)✓ Integration tests passed$(RESET)" - -test-functional: ## Run functional tests only - @echo -e "$(BLUE)→ Running functional tests...$(RESET)" - @$(PHPUNIT) --colors=always --testdox --testsuite=Functional - @echo -e "$(GREEN)✓ Functional tests passed$(RESET)" - -coverage: ## Generate code coverage report - @echo -e "$(BLUE)→ Generating code coverage report...$(RESET)" - @mkdir -p $(COVERAGE_DIR) - @XDEBUG_MODE=coverage $(PHPUNIT) --coverage-html $(COVERAGE_DIR)/html \ - --coverage-clover $(COVERAGE_DIR)/clover.xml \ - --coverage-text=$(COVERAGE_DIR)/coverage.txt - @echo -e "$(GREEN)✓ Coverage report generated: $(COVERAGE_DIR)/html/index.html$(RESET)" - -coverage-text: ## Show coverage in terminal - @echo -e "$(BLUE)→ Generating text coverage...$(RESET)" - @XDEBUG_MODE=coverage $(PHPUNIT) --coverage-text - -mutation: ## Run mutation testing - @echo -e "$(BLUE)→ Running mutation tests...$(RESET)" - @mkdir -p $(CACHE_DIR)/infection - @XDEBUG_MODE=coverage $(PHP) -d pcov.enabled=0 $(INFECTION) --threads=4 --min-msi=80 --min-covered-msi=90 --show-mutations - @echo -e "$(GREEN)✓ Mutation testing complete$(RESET)" - -mutation-report: ## Generate mutation testing report - @echo -e "$(BLUE)→ Generating mutation report...$(RESET)" - @XDEBUG_MODE=coverage $(PHP) -d pcov.enabled=0 $(INFECTION) --threads=4 --min-msi=80 --min-covered-msi=90 \ - --log-verbosity=all --show-mutations - @echo -e "$(GREEN)✓ Report generated: infection.html$(RESET)" - -# ============================================================================== -# STATIC ANALYSIS -# ============================================================================== - -analyse: ## Run all static analysis tools - @echo -e "$(BLUE)→ Running static analysis...$(RESET)" - @$(MAKE) phpstan - @$(MAKE) psalm - @$(MAKE) cs-check - @echo -e "$(GREEN)✓ All analysis passed$(RESET)" - -phpstan: ## Run PHPStan analysis - @echo -e "$(BLUE)→ Running PHPStan...$(RESET)" - @mkdir -p $(CACHE_DIR)/phpstan - @if [ -n "$$(find $(SRC_DIR) -name '*.php' 2>/dev/null)" ]; then \ - $(PHPSTAN) analyse $(SRC_DIR) --level=max --memory-limit=512M && \ - echo -e "$(GREEN)✓ PHPStan analysis passed$(RESET)"; \ - else \ - echo -e "$(YELLOW)⚠ No PHP files found in $(SRC_DIR), skipping PHPStan$(RESET)"; \ - fi - -phpstan-baseline: ## Generate PHPStan baseline - @echo -e "$(BLUE)→ Generating PHPStan baseline...$(RESET)" - @$(PHPSTAN) analyse $(SRC_DIR) --level=max --generate-baseline - @echo -e "$(GREEN)✓ Baseline generated: phpstan-baseline.neon$(RESET)" - -psalm: ## Run Psalm analysis - @echo -e "$(BLUE)→ Running Psalm...$(RESET)" - @$(PSALM) --show-info=true --stats --no-cache - @echo -e "$(GREEN)✓ Psalm analysis passed$(RESET)" - -psalm-baseline: ## Generate Psalm baseline - @echo -e "$(BLUE)→ Generating Psalm baseline...$(RESET)" - @$(PSALM) --set-baseline=psalm-baseline.xml - @echo -e "$(GREEN)✓ Baseline generated: psalm-baseline.xml$(RESET)" - -psalm-taint: ## Run Psalm taint analysis - @echo -e "$(BLUE)→ Running Psalm taint analysis...$(RESET)" - @$(PSALM) --taint-analysis - @echo -e "$(GREEN)✓ Taint analysis complete$(RESET)" - -# ============================================================================== -# CODE STYLE & FORMATTING -# ============================================================================== - -cs-check: ## Check coding standards (PHPCS) - @echo -e "$(BLUE)→ Checking coding standards...$(RESET)" - @$(PHPCS) --standard=phpcs.xml --colors $(SRC_DIR) $(TEST_DIR) - @echo -e "$(GREEN)✓ Coding standards check passed$(RESET)" - -cbf-fix: ## Fix coding standards (PHPCS) - @echo -e "$(BLUE)→ Fixing coding standards...$(RESET)" - @$(PHPCBF) --standard=phpcs.xml --colors $(SRC_DIR) $(TEST_DIR) - @echo -e "$(GREEN)✓ Coding standards fixed$(RESET)" - -format: ## Format code with PHP-CS-Fixer - @echo -e "$(BLUE)→ Formatting code...$(RESET)" - @$(PHP_CS_FIXER) fix --config=.php-cs-fixer.php --verbose --diff - @$(MAKE) cbf-fix - @echo -e "$(GREEN)✓ Code formatted$(RESET)" - -format-dry: ## Show formatting changes without applying - @echo -e "$(BLUE)→ Dry-run code formatting...$(RESET)" - @$(PHP_CS_FIXER) fix --config=.php-cs-fixer.php --verbose --diff --dry-run - -lint: ## Lint PHP files for syntax errors - @echo -e "$(BLUE)→ Linting PHP files...$(RESET)" - @find $(SRC_DIR) $(TEST_DIR) -name "*.php" -print0 | xargs -0 -n1 $(PHP) -l > /dev/null - @echo -e "$(GREEN)✓ All PHP files are valid$(RESET)" diff --git a/.make/local/Makefile.setup.mk b/.make/local/Makefile.setup.mk deleted file mode 100644 index 8441953..0000000 --- a/.make/local/Makefile.setup.mk +++ /dev/null @@ -1,109 +0,0 @@ -# ============================================================================== -# KaririCode\DevKit - Setup, Install & Clean Targets -# ============================================================================== - -.PHONY: check-php install install-dev fresh-install update verify-install \ - clean clean-all validate security security-strict outdated - -# ============================================================================== -# SETUP & INSTALLATION -# ============================================================================== - -check-php: ## Check PHP version requirement - $(call check_php_version) - -install: check-php ## Install dependencies - @echo -e "$(BLUE)→ Installing Composer dependencies...$(RESET)" - @if ! $(COMPOSER) validate --no-check-publish 2>/dev/null; then \ - echo -e "$(YELLOW)⚠ composer.lock outdated, updating dependencies...$(RESET)"; \ - $(COMPOSER) update --with-all-dependencies --no-interaction --prefer-dist --optimize-autoloader; \ - else \ - $(COMPOSER) install --no-interaction --prefer-dist --optimize-autoloader; \ - fi - @$(MAKE) verify-install - @echo -e "$(GREEN)✓ Installation complete$(RESET)" - -install-dev: check-php ## Install dependencies with dev tools - @echo -e "$(BLUE)→ Installing Composer dependencies (dev mode)...$(RESET)" - @$(COMPOSER) install --no-interaction --prefer-dist - @$(MAKE) verify-install - @echo -e "$(GREEN)✓ Development installation complete$(RESET)" - -fresh-install: check-php ## Fresh install (removes lock file) - @echo -e "$(BLUE)→ Removing composer.lock...$(RESET)" - @rm -f composer.lock - @echo -e "$(BLUE)→ Installing fresh dependencies...$(RESET)" - @$(COMPOSER) install --no-interaction --prefer-dist --optimize-autoloader - @$(MAKE) verify-install - @echo -e "$(GREEN)✓ Fresh installation complete$(RESET)" - -update: ## Update dependencies - @echo -e "$(BLUE)→ Updating Composer dependencies...$(RESET)" - @$(COMPOSER) update --with-all-dependencies --no-interaction --prefer-dist --optimize-autoloader - @echo -e "$(GREEN)✓ Dependencies updated$(RESET)" - -verify-install: ## Verify installation - @echo -e "$(BLUE)→ Verifying installation...$(RESET)" - $(call check_file,vendor/autoload.php,Autoloader) - $(call check_file,$(PHPUNIT),PHPUnit) - $(call check_file,$(PHPSTAN),PHPStan) - @echo -e "$(GREEN)✓ Installation verified$(RESET)" - -# ============================================================================== -# CLEANUP -# ============================================================================== - -clean: ## Clean build artifacts and caches - @echo -e "$(BLUE)→ Cleaning build artifacts...$(RESET)" - @rm -rf $(BUILD_DIR) - @rm -rf $(COVERAGE_DIR) - @rm -rf $(REPORTS_DIR) - @rm -rf $(CACHE_DIR) - @rm -rf .phpunit.cache - @rm -rf .phpunit.result.cache - @rm -rf .php-cs-fixer.cache - @rm -f infection.log infection.html - @echo -e "$(GREEN)✓ Clean complete$(RESET)" - -clean-all: clean ## Clean everything including vendor - @echo -e "$(BLUE)→ Removing vendor directory...$(RESET)" - @rm -rf vendor - @rm -f composer.lock - @echo -e "$(GREEN)✓ Deep clean complete$(RESET)" - -# ============================================================================== -# VALIDATION & SECURITY -# ============================================================================== - -validate: ## Validate composer.json - @echo -e "$(BLUE)→ Validating composer.json...$(RESET)" - @if [ ! -f "$(COMPOSER_BIN)" ]; then \ - echo -e "$(RED)✗ Composer not found. Please install Composer first.$(RESET)"; \ - echo -e "$(YELLOW) Visit: https://getcomposer.org/download/$(RESET)"; \ - exit 1; \ - fi - @$(COMPOSER) validate --strict --no-check-publish - @echo -e "$(GREEN)✓ composer.json is valid$(RESET)" - -security: ## Check for security vulnerabilities - @echo -e "$(BLUE)→ Checking for security vulnerabilities...$(RESET)" - @if $(COMPOSER) audit --no-dev --locked 2>&1 | grep -q "security vulnerability"; then \ - echo -e "$(RED)✗ Security vulnerabilities found$(RESET)"; \ - $(COMPOSER) audit --no-dev --locked; \ - exit 1; \ - elif $(COMPOSER) audit --no-dev --locked 2>&1 | grep -q "abandoned"; then \ - echo -e "$(YELLOW)⚠ Found abandoned packages (informational only):$(RESET)"; \ - $(COMPOSER) audit --no-dev --locked || true; \ - echo -e "$(GREEN)✓ No security vulnerabilities found$(RESET)"; \ - else \ - echo -e "$(GREEN)✓ No security vulnerabilities found$(RESET)"; \ - fi - -security-strict: ## Check for security vulnerabilities (strict mode) - @echo -e "$(BLUE)→ Checking for security vulnerabilities (strict)...$(RESET)" - @$(COMPOSER) audit - @echo -e "$(GREEN)✓ No security vulnerabilities or abandoned packages$(RESET)" - -outdated: ## Check for outdated dependencies - @echo -e "$(BLUE)→ Checking for outdated dependencies...$(RESET)" - @$(COMPOSER) outdated --direct diff --git a/.make/pipeline/Makefile.orchestration.mk b/.make/pipeline/Makefile.orchestration.mk deleted file mode 100644 index c31e534..0000000 --- a/.make/pipeline/Makefile.orchestration.mk +++ /dev/null @@ -1,53 +0,0 @@ -# ============================================================================== -# KaririCode\DevKit - CI/CD Orchestration -# ============================================================================== -# Orquestra pipelines de CI/CD com composição de targets -# ============================================================================== - -.PHONY: check ci ci-full cd pre-commit - -# --- Quality Checks --- -check: lint analyse test ## Run all quality checks - @echo -e "$(GREEN)✓ All quality checks passed$(RESET)" - -# --- CI Pipeline --- -ci: ## Run CI pipeline (fast checks) - $(call pipeline_header,"KaririCode\\DevKit CI Pipeline") - @$(MAKE) --no-print-directory check-php - @$(MAKE) --no-print-directory lint - @$(MAKE) --no-print-directory cs-check - @$(MAKE) --no-print-directory phpstan - @$(MAKE) --no-print-directory psalm - @$(MAKE) --no-print-directory test - @echo -e "$(BOLD)$(GREEN)✓ CI pipeline completed successfully$(RESET)" - -# --- Full CI Pipeline --- -ci-full: ## Run full CI pipeline (with coverage) - $(call pipeline_header,"KaririCode\\DevKit Full CI Pipeline") - @$(MAKE) --no-print-directory check-php - @$(MAKE) --no-print-directory validate - @$(MAKE) --no-print-directory security - @$(MAKE) --no-print-directory lint - @$(MAKE) --no-print-directory cs-check - @$(MAKE) --no-print-directory phpstan - @$(MAKE) --no-print-directory psalm - @$(MAKE) --no-print-directory test - @$(MAKE) --no-print-directory coverage - @$(MAKE) --no-print-directory mutation - @echo -e "$(BOLD)$(GREEN)✓ Full CI pipeline completed successfully$(RESET)" - -# --- CD Pipeline --- -cd: ## Run CD pipeline (release preparation) - $(call pipeline_header,"KaririCode\\DevKit CD Pipeline") - @$(MAKE) --no-print-directory ci-full - @$(MAKE) --no-print-directory bench - @echo -e "$(BOLD)$(GREEN)✓ CD pipeline completed - Ready for release$(RESET)" - -# --- Pre-commit Hook --- -pre-commit: ## Run pre-commit checks - @echo -e "$(BLUE)→ Running pre-commit checks...$(RESET)" - @$(MAKE) --no-print-directory format - @$(MAKE) --no-print-directory lint - @$(MAKE) --no-print-directory analyse - @$(MAKE) --no-print-directory test-unit - @echo -e "$(GREEN)✓ Pre-commit checks passed$(RESET)" diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php deleted file mode 100644 index fb451a1..0000000 --- a/.php-cs-fixer.php +++ /dev/null @@ -1,235 +0,0 @@ - - * @since 1.0.0 - */ - -$finder = PhpCsFixer\Finder::create() - ->in([ - __DIR__ . '/src', - __DIR__ . '/tests', - ]) - ->name('*.php') - ->ignoreDotFiles(true) - ->ignoreVCS(true) - ->notPath('vendor'); - -return (new PhpCsFixer\Config()) - ->setRiskyAllowed(true) - ->setRules([ - // ==================================================================== - // BASE RULE SETS - // ==================================================================== - '@PSR12' => true, - '@PHP8x3Migration' => true, // Usar PHP83 em vez de PHP84 (mais estável) - - // ==================================================================== - // CRITICAL: COMMENT PRESERVATION RULES - // ==================================================================== - 'no_empty_phpdoc' => false, - 'no_superfluous_phpdoc_tags' => false, - 'phpdoc_no_useless_inheritdoc' => false, - 'phpdoc_no_access' => false, - 'phpdoc_no_package' => false, - 'phpdoc_summary' => false, - 'phpdoc_order' => false, - 'phpdoc_separation' => false, - 'phpdoc_tag_type' => false, - 'phpdoc_to_comment' => false, - 'phpdoc_add_missing_param_annotation' => false, - 'no_blank_lines_after_phpdoc' => false, - 'phpdoc_align' => false, - 'phpdoc_indent' => false, - 'phpdoc_trim' => false, - 'no_trailing_whitespace_in_comment' => false, - 'single_line_comment_style' => false, - 'multiline_comment_opening_closing' => false, - 'comment_to_phpdoc' => false, - 'no_empty_comment' => false, - - // ==================================================================== - // ARRAYS - // ==================================================================== - 'array_syntax' => ['syntax' => 'short'], - 'no_whitespace_before_comma_in_array' => true, - 'whitespace_after_comma_in_array' => true, - 'trim_array_spaces' => true, - 'normalize_index_brace' => true, - 'trailing_comma_in_multiline' => [ - 'elements' => ['arrays', 'arguments', 'parameters'], - ], - - // ==================================================================== - // BLANK LINES - // ==================================================================== - 'blank_line_after_namespace' => true, - 'blank_line_after_opening_tag' => true, - 'blank_line_before_statement' => [ - 'statements' => ['return', 'throw', 'try'], - ], - // 'blank_lines_before_namespace' => [ - // 'min_line_breaks' => 1, - // 'max_line_breaks' => 1, - // ], - 'no_blank_lines_after_class_opening' => true, - 'no_extra_blank_lines' => [ - 'tokens' => ['extra', 'throw', 'use'], - ], - - // ==================================================================== - // CASTS - // ==================================================================== - 'cast_spaces' => ['space' => 'single'], - 'lowercase_cast' => true, - 'short_scalar_cast' => true, - 'no_unset_cast' => true, - - // ==================================================================== - // CLASSES - // ==================================================================== - 'class_attributes_separation' => [ - 'elements' => [ - 'const' => 'one', - 'method' => 'one', - 'property' => 'one', - 'trait_import' => 'none', - ], - ], - 'single_class_element_per_statement' => true, - 'modifier_keywords' => true, // Substitui visibility_required - 'no_null_property_initialization' => true, - 'self_accessor' => true, - - // ==================================================================== - // CONTROL STRUCTURES - // ==================================================================== - 'no_alternative_syntax' => true, - 'no_superfluous_elseif' => true, - 'no_useless_else' => true, - 'simplified_if_return' => true, - 'yoda_style' => false, - 'elseif' => true, - - // ==================================================================== - // FUNCTIONS - // ==================================================================== - 'function_declaration' => ['closure_function_spacing' => 'one'], - 'type_declaration_spaces' => true, // Substitui function_typehint_space - 'method_argument_space' => [ - 'on_multiline' => 'ensure_fully_multiline', - ], - 'no_spaces_after_function_name' => true, - 'return_type_declaration' => ['space_before' => 'none'], - 'void_return' => true, - 'native_function_casing' => true, - 'native_type_declaration_casing' => true, // Substitui native_function_type_declaration_casing - - // ==================================================================== - // IMPORTS - // ==================================================================== - 'no_unused_imports' => true, - 'ordered_imports' => [ - 'sort_algorithm' => 'alpha', - 'imports_order' => ['class', 'function', 'const'], - ], - 'single_import_per_statement' => true, - 'single_line_after_imports' => true, - 'no_leading_import_slash' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => true, - ], - 'fully_qualified_strict_types' => true, - - // ==================================================================== - // NAMESPACES - // ==================================================================== - 'no_leading_namespace_whitespace' => false, - - // ==================================================================== - // OPERATORS - // ==================================================================== - 'binary_operator_spaces' => [ - 'default' => 'single_space', - ], - 'concat_space' => ['spacing' => 'one'], - 'unary_operator_spaces' => true, - 'ternary_operator_spaces' => true, - 'new_with_parentheses' => true, - 'object_operator_without_whitespace' => true, - 'standardize_not_equals' => true, - 'ternary_to_null_coalescing' => true, - - // ==================================================================== - // PHP TAGS - // ==================================================================== - 'full_opening_tag' => true, - 'no_closing_tag' => true, - - // ==================================================================== - // PHPDOC (SAFE RULES) - // ==================================================================== - 'phpdoc_scalar' => true, - 'phpdoc_single_line_var_spacing' => true, - 'phpdoc_types' => true, - 'phpdoc_var_without_name' => true, - - // ==================================================================== - // SEMICOLONS - // ==================================================================== - 'no_empty_statement' => true, - 'no_singleline_whitespace_before_semicolons' => true, - 'semicolon_after_instruction' => true, - 'space_after_semicolon' => [ - 'remove_in_empty_for_expressions' => true, - ], - - // ==================================================================== - // STRINGS - // ==================================================================== - 'single_quote' => true, - 'simple_to_complex_string_variable' => true, - - // ==================================================================== - // WHITESPACE - // ==================================================================== - 'no_trailing_whitespace' => true, - 'no_whitespace_in_blank_line' => true, - 'single_blank_line_at_eof' => true, - 'statement_indentation' => true, - - // ==================================================================== - // STRICT TYPING & SAFETY - // ==================================================================== - 'declare_strict_types' => true, - 'strict_comparison' => true, - 'strict_param' => true, - - // ==================================================================== - // PHP 8.4+ FEATURES - // ==================================================================== - 'modernize_types_casting' => true, - 'no_alias_functions' => true, - 'no_mixed_echo_print' => ['use' => 'echo'], - - // ==================================================================== - // CODE CLEANUP - // ==================================================================== - 'no_unreachable_default_argument_value' => true, - 'no_useless_return' => true, - 'combine_consecutive_issets' => true, - 'combine_consecutive_unsets' => true, - ]) - ->setFinder($finder) - ->setCacheFile(__DIR__ . '/.php-cs-fixer.cache') - ->setIndent(' ') - ->setLineEnding("\n"); diff --git a/.vscode/README.md b/.vscode/README.md deleted file mode 100644 index c243ced..0000000 --- a/.vscode/README.md +++ /dev/null @@ -1,409 +0,0 @@ -# VS Code Configuration - KaririCode\Parser - -Professional VS Code workspace configuration for KaririCode\Parser development, integrating all quality tools and preserving premium documentation standards. - -## 📁 Files Overview - -``` -.vscode/ -├── settings.json # Workspace settings (tools integration) -├── extensions.json # Recommended extensions -├── tasks.json # Makefile integration -├── launch.json # Debug configurations -├── php.code-snippets # Documentation snippets -└── README.md # This file -``` - -## 🚀 Quick Start - -### 1. Install Required Tools - -First, ensure your system has PHP 8.4+ and Composer: - -```bash -php -v # Should show 8.4+ -composer --version -``` - -### 2. Install Project Dependencies - -```bash -make install -# or manually: -composer install -``` - -### 3. Install VS Code Extensions - -Open VS Code and press `Ctrl+Shift+P` (or `Cmd+Shift+P` on macOS), then: - -1. Type: `Extensions: Show Recommended Extensions` -2. Click "Install All" or install individually: - - **Essential**: Intelephense, PHP-CS-Fixer, PHPStan, Psalm - - **Highly Recommended**: Better Comments, Error Lens, Todo Tree - - **Optional**: See `extensions.json` for full list - -### 4. Configure XDebug (Optional) - -For debugging, install and configure XDebug 3.x: - -```bash -# Ubuntu/Debian -sudo apt-get install php8.4-xdebug - -# macOS (Homebrew) -brew install php@8.4-xdebug -``` - -Add to `php.ini` (find with `php --ini`): - -```ini -[XDebug] -zend_extension=xdebug.so -xdebug.mode=debug,coverage -xdebug.start_with_request=yes -xdebug.client_port=9003 -xdebug.client_host=127.0.0.1 -xdebug.idekey=VSCODE -``` - -Restart PHP and verify: - -```bash -php -v # Should show "with Xdebug" -``` - -## 🛠️ Tool Integration - -### PHP-CS-Fixer - -**What it does**: Formats PHP code according to PSR-12 and custom rules while **preserving premium comments**. - -**How to use**: -- Via Command Palette: `Format Document` (when PHP file is active) -- Via Task: `Ctrl+Shift+B` → "Format Code (PHP-CS-Fixer)" -- Via Makefile: `make format` -- Via Terminal: `vendor/bin/php-cs-fixer fix` - -**Important**: Auto-format on save is **disabled** to preserve premium documentation. Format manually when needed. - -### PHPStan - -**What it does**: Static analysis at maximum strictness level. - -**How to use**: -- Automatic: Errors appear in "Problems" panel as you type -- Via Task: `Ctrl+Shift+B` → "Run PHPStan" -- Via Makefile: `make phpstan` -- Via Terminal: `vendor/bin/phpstan analyse` - -**Configuration**: `phpstan.neon` (level: max) - -### Psalm - -**What it does**: Additional static analysis with different heuristics. - -**How to use**: -- Automatic: Errors appear alongside PHPStan -- Via Task: `Ctrl+Shift+B` → "Run Psalm" -- Via Makefile: `make psalm` -- Via Terminal: `vendor/bin/psalm` - -**Configuration**: `psalm.xml` (errorLevel: 1) - -### PHPCS (PHP_CodeSniffer) - -**What it does**: Checks PSR-12 compliance with custom rules. - -**How to use**: -- Automatic: Warnings appear in "Problems" panel -- Via Task: "Check Coding Standards" -- Via Makefile: `make cs-check` -- Auto-fix: `make cs-fix` - -**Configuration**: `phpcs.xml` - -### PHPUnit - -**What it does**: Runs test suites. - -**How to use**: -- Via Test Explorer (sidebar icon) -- Via Task: `Ctrl+Shift+B` → "Run All Tests" -- Via Debug: `F5` → Select "PHPUnit: Debug..." -- Via Makefile: `make test` - -**Configuration**: `phpunit.xml` - -## ⌨️ Keyboard Shortcuts - -### Essential Commands - -| Shortcut | Action | -|----------|--------| -| `Ctrl+Shift+B` | Open Build Tasks menu | -| `F5` | Start debugging | -| `Shift+F5` | Stop debugging | -| `F9` | Toggle breakpoint | -| `F10` | Step over | -| `F11` | Step into | -| `Shift+F11` | Step out | -| `Ctrl+Shift+P` | Command Palette | -| `Ctrl+P` | Quick Open File | -| `Ctrl+Shift+F` | Search in Files | - -### Custom Tasks (via `Ctrl+Shift+B`) - -**Testing**: -- Run All Tests -- Run Unit Tests -- Run Integration Tests -- Generate Coverage Report -- Run Mutation Tests - -**Analysis**: -- Run All Analysis -- Run PHPStan -- Run Psalm -- Check Coding Standards - -**Formatting**: -- Format Code (PHP-CS-Fixer) -- Fix Coding Standards -- Lint PHP Files - -**Quality**: -- Run All Quality Checks -- Run CI Pipeline -- Run Full CI Pipeline -- Run Pre-Commit Checks - -**Docker**: -- Docker: Run Tests -- Docker: Run CI -- Docker: Open Shell - -## 🐛 Debugging Guide - -### Debug Current Test File - -1. Open test file (e.g., `tests/Unit/SomeTest.php`) -2. Press `F5` -3. Select "PHPUnit: Debug Current Test File" -4. Set breakpoints with `F9` - -### Debug Specific Test Method - -1. Select test method name -2. Press `F5` -3. Select "PHPUnit: Debug Current Test Method" - -### Debug PHP Script - -1. Open PHP file -2. Press `F5` -3. Select "Launch Currently Open Script" - -### Listen for XDebug (Browser/CLI) - -1. Press `F5` -2. Select "Listen for XDebug" -3. Trigger PHP execution with `XDEBUG_TRIGGER=1` - -Example: -```bash -XDEBUG_MODE=debug XDEBUG_TRIGGER=1 php script.php -``` - -## 📝 Code Snippets - -Type these prefixes and press `Tab`: - -### Documentation - -| Prefix | Description | -|--------|-------------| -| `kc-class-doc` | Premium class documentation (800-1200 lines) | -| `kc-method-doc` | Method documentation with examples | -| `kc-doc` | Simple PHPDoc block | -| `kc-prop-doc` | Property documentation | -| `kc-perf` | Performance annotation | -| `kc-algo` | Algorithm documentation | - -### Templates - -| Prefix | Description | -|--------|-------------| -| `kc-class` | Class template | -| `kc-interface` | Interface template | -| `kc-trait` | Trait template | -| `kc-enum` | Enum template | -| `kc-exception` | Exception class | -| `kc-test` | Test class template | -| `kc-test-method` | Test method | - -### Common Patterns - -| Prefix | Description | -|--------|-------------| -| `kc-construct` | Constructor with promoted properties | -| `kc-get` | Getter method | - -Example usage: - -```php -// Type 'kc-class-doc' and press Tab -/** - * Brief description of the class - * - * Detailed description... - * [Full premium documentation template] - */ -``` - -## 🔧 Troubleshooting - -### Extensions Not Working - -1. Reload VS Code: `Ctrl+Shift+P` → "Developer: Reload Window" -2. Check extension status: `Ctrl+Shift+X` -3. Verify paths in `settings.json`: - ```json - "phpstan.path": "${workspaceFolder}/vendor/bin/phpstan" - ``` -4. Ensure Composer dependencies installed: `make install` - -### XDebug Not Connecting - -1. Verify XDebug installed: - ```bash - php -m | grep xdebug - ``` -2. Check XDebug configuration: - ```bash - php -i | grep xdebug - ``` -3. Verify port 9003 is not in use: - ```bash - netstat -an | grep 9003 - ``` -4. Check `launch.json` port matches `php.ini`: - ```json - "port": 9003 - ``` - -### PHPStan/Psalm Errors Not Showing - -1. Open "Output" panel (`Ctrl+Shift+U`) -2. Select "PHPStan" or "Psalm" from dropdown -3. Check for error messages -4. Verify cache directory writable: - ```bash - mkdir -p var/cache/{phpstan,psalm} - chmod 755 var/cache/{phpstan,psalm} - ``` - -### Format Not Working - -1. Ensure PHP-CS-Fixer installed: - ```bash - test -f vendor/bin/php-cs-fixer && echo "OK" - ``` -2. Check `.php-cs-fixer.php` exists -3. Run manually to see errors: - ```bash - make format - ``` -4. Verify VS Code setting: - ```json - "[php]": { - "editor.defaultFormatter": "junstyle.php-cs-fixer" - } - ``` - -### Tests Not Found in Test Explorer - -1. Reload tests: Click refresh icon in Test Explorer -2. Verify PHPUnit config: - ```bash - vendor/bin/phpunit --list-tests - ``` -3. Check `phpunit.xml` is valid XML -4. Restart PHP Language Server: - `Ctrl+Shift+P` → "PHP: Restart Language Server" - -## 📚 Additional Resources - -### Documentation -- [PHP-CS-Fixer Rules](https://cs.symfony.com/) -- [PHPStan Levels](https://phpstan.org/user-guide/rule-levels) -- [Psalm Error Levels](https://psalm.dev/docs/running_psalm/error_levels/) -- [PHPUnit Documentation](https://phpunit.de/documentation.html) -- [XDebug Documentation](https://xdebug.org/docs/) - -### KaririCode Standards -- [COMMENTING_GUIDELINES.md](../COMMENTING_GUIDELINES.md) - Premium documentation philosophy -- [Makefile](../Makefile) - Development workflow automation -- [composer.json](../composer.json) - Dependencies and scripts - -### VS Code -- [PHP Development](https://code.visualstudio.com/docs/languages/php) -- [Debugging](https://code.visualstudio.com/docs/editor/debugging) -- [Tasks](https://code.visualstudio.com/docs/editor/tasks) - -## 🔐 Security Note - -**Never commit**: -- `.vscode/*.log` - Debug logs -- `.vscode/settings.local.json` - Personal overrides -- XDebug profiler output - -These are already in `.gitignore`. - -## 🎯 Pro Tips - -1. **Split View Testing**: Open test and source side-by-side with `Ctrl+\` - -2. **Quick Test Run**: Use Makefile shortcuts - ```bash - make test-unit # Fast unit tests - make coverage # Generate coverage - make mutation # Mutation testing - ``` - -3. **Multi-cursor Editing**: `Alt+Click` to add cursors, great for batch PHPDoc updates - -4. **Search in Tests Only**: - - Press `Ctrl+Shift+F` - - Click "..." → "files to include" - - Enter: `tests/**/*.php` - -5. **Format Only Specific File**: - ```bash - vendor/bin/php-cs-fixer fix src/Specific/File.php - ``` - -6. **Quick CI Check Before Commit**: - ```bash - make pre-commit - ``` - -7. **Docker Isolation**: Use Docker tasks for CI/CD consistency - ```bash - make docker-ci - ``` - -8. **Zen Mode**: `Ctrl+K Z` for distraction-free coding - -## 🆘 Support - -Issues with VS Code configuration? Check: -1. This README first -2. [COMMENTING_GUIDELINES.md](../COMMENTING_GUIDELINES.md) -3. Project issues: https://github.com/KaririCode-Framework/kariricode-parser/issues - ---- - -**Last Updated**: 2025-01-27 -**Version**: 1.0.0 -**Maintainer**: Walmir Silva diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 160050e..0000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,171 +0,0 @@ -{ - // ============================================================================ - // KaririCode\Parser - Recommended VS Code Extensions - // ============================================================================ - // Essential and recommended extensions for PHP 8.4+ development with - // KaririCode quality standards - // - // Last Updated: 2025-01-27 - // Validated against: VS Code marketplace latest releases - // ============================================================================ - - "recommendations": [ - // ===== ESSENTIAL EXTENSIONS ===== - // These extensions are critical for KaririCode development workflow - - // PHP Language Server - Primary IntelliSense - // Provides fast code completion, go to definition, workspace symbols - // Zero dependencies, highly performant - "bmewburn.vscode-intelephense-client", - - // PHP-CS-Fixer Integration - // Auto-formats PHP code according to PSR-12 and custom rules - // CRITICAL: Preserves premium documentation comments (800-1200 lines) - "junstyle.php-cs-fixer", - - // PHPStan Static Analysis - // Static analyzer at maximum strictness level (level: max) - // Detects bugs and type errors before runtime - "swordev.phpstan", - - // Psalm Static Analysis - // Additional static analysis with different heuristics - // Complements PHPStan for comprehensive type checking - "getpsalm.psalm-vscode-plugin", - - // PHP Sniffer & Beautifier - // Integrates phpcs (linting) + phpcbf (auto-fix) - // Best PHPCS extension validated by Drupal community (2025) - // Multi-root workspace support, works out-of-the-box - "ValeryanM.vscode-phpsab", - - // PHPUnit Test Runner - // Runs tests directly from VS Code with Test Explorer integration - // Supports debugging individual tests - "recca0120.vscode-phpunit", - - // PHP Debug - // XDebug 3.x integration for debugging and profiling - // Essential for development and troubleshooting - "xdebug.php-debug", - - // EditorConfig Support - // Maintains consistent coding styles between editors - // Automatically applies .editorconfig rules - "editorconfig.editorconfig", - - // ===== HIGHLY RECOMMENDED ===== - // Strongly recommended for productivity and code quality - - // Better Comments - // Enhances comment visibility with color coding - // Perfect for premium documentation with TODO, FIXME, NOTE tags - "aaron-bond.better-comments", - - // Error Lens - // Displays errors and warnings inline - // Improves error visibility and debugging speed - "usernamehw.errorlens", - - // Todo Tree - // Aggregates TODO, FIXME, BUG, OPTIMIZE comments - // Searchable tree view for task tracking - "gruntfuggly.todo-tree", - - // GitLens - // Advanced Git integration and history visualization - // Blame annotations, commit search, powerful comparisons - "eamodio.gitlens", - - // Git Graph - // Visual Git commit history graph - // Complements GitLens for repository visualization - "mhutchie.git-graph", - - // PHP Namespace Resolver - // Auto-imports and resolves PHP namespaces - // Saves time on use statements - "mehedidracula.php-namespace-resolver", - - // ===== OPTIONAL BUT USEFUL ===== - // Enhance specific workflows when needed - - // Docker Integration - // Manage containers, images, and compose files - // Essential if using Docker for development - "ms-azuretools.vscode-docker", - - // YAML Support - // Syntax highlighting and validation for YAML files - // Used in phpstan.neon, docker-compose.yml, CI/CD configs - "redhat.vscode-yaml", - - // XML Tools - // XML formatting, validation, and XPath evaluation - // Used in phpunit.xml, phpcs.xml, psalm.xml - "redhat.vscode-xml", - - // Markdown All in One - // Enhanced Markdown editing with preview and shortcuts - // Useful for README.md and documentation files - "yzhang.markdown-all-in-one", - - // REST Client - // Test HTTP requests directly from .http files - // Alternative to Postman for API testing - "humao.rest-client", - - // Makefile Tools - // Syntax highlighting and IntelliSense for Makefiles - // KaririCode uses Makefile for automation - "ms-vscode.makefile-tools", - - // DotENV Syntax - // Syntax highlighting for .env files - // Common in PHP projects for configuration - "mikestead.dotenv", - - // PHP DocBlocker - // Assists with PHPDoc generation (optional) - // KaririCode primarily uses custom snippets, but can help - "neilbrayfield.php-docblocker" - - // ===== AI CODING ASSISTANTS (OPTIONAL) ===== - // Uncomment ONE of these if desired: - // "github.copilot", // GitHub Copilot (paid, most popular) - // "codeium.codeium", // Codeium (free alternative) - // "continue.continue" // Continue (open-source, local LLMs) - ], - - // ===== NOT RECOMMENDED ===== - // Extensions that conflict with quality standards or are deprecated - "unwantedRecommendations": [ - // ===== FORMATTING CONFLICTS ===== - // These interfere with PHP-CS-Fixer configuration - "esbenp.prettier-vscode", - "hookyqr.beautify", - - // ===== INTELLISENSE CONFLICTS ===== - // These conflict with Intelephense or provide duplicate functionality - "vscode.php-language-features", - "zobo.php-intellisense", - "ikappas.composer", - - // ===== DEPRECATED/ABANDONED ===== - // These extensions are no longer maintained or have better alternatives - "coenraads.bracket-pair-colorizer-2", - "calebporzio.better-phpunit", - "phproberto.vscode-php-getters-setters", - "christian-kohler.path-intellisense", - - // ===== PHPCS ALTERNATIVES (INFERIOR) ===== - // Validated by Drupal.org (August 2025) as problematic - "ikappas.phpcs", - "shevaua.phpcs", - "obliviousharmony.vscode-php-codesniffer", - - // ===== LOWER QUALITY TOOLS ===== - "dotjoshjohnson.xml", - "streetsidesoftware.code-spell-checker" - ] -} diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index cbd3a07..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,175 +0,0 @@ -{ - // ============================================================================ - // KaririCode\Parser - VS Code Debug Configurations - // ============================================================================ - // XDebug 3.x configurations for debugging PHP code and tests - // ============================================================================ - - "version": "0.2.0", - "configurations": [ - // ===== XDEBUG - LISTEN FOR CONNECTIONS ===== - { - "name": "Listen for XDebug", - "type": "php", - "request": "launch", - "port": 9003, - "log": true, - "pathMappings": { - "/app": "${workspaceFolder}" - }, - "xdebugSettings": { - "max_data": 65535, - "show_hidden": 1, - "max_children": 100, - "max_depth": 5 - } - }, - - // ===== XDEBUG - LAUNCH CURRENT FILE ===== - { - "name": "Launch Currently Open Script", - "type": "php", - "request": "launch", - "program": "${file}", - "cwd": "${fileDirname}", - "port": 9003, - "runtimeArgs": ["-dxdebug.mode=debug", "-dxdebug.start_with_request=yes"], - "env": { - "XDEBUG_MODE": "debug", - "XDEBUG_CONFIG": "client_port=9003" - } - }, - - // ===== PHPUNIT - DEBUG CURRENT TEST FILE ===== - { - "name": "PHPUnit: Debug Current Test File", - "type": "php", - "request": "launch", - "program": "${workspaceFolder}/vendor/bin/phpunit", - "args": ["${file}", "--colors=always", "--testdox"], - "cwd": "${workspaceFolder}", - "port": 9003, - "runtimeArgs": ["-dxdebug.mode=debug", "-dxdebug.start_with_request=yes"], - "env": { - "XDEBUG_MODE": "debug,coverage" - } - }, - - // ===== PHPUNIT - DEBUG SPECIFIC TEST METHOD ===== - { - "name": "PHPUnit: Debug Current Test Method", - "type": "php", - "request": "launch", - "program": "${workspaceFolder}/vendor/bin/phpunit", - "args": [ - "${file}", - "--filter", - "${selectedText}", - "--colors=always", - "--testdox" - ], - "cwd": "${workspaceFolder}", - "port": 9003, - "runtimeArgs": ["-dxdebug.mode=debug", "-dxdebug.start_with_request=yes"], - "env": { - "XDEBUG_MODE": "debug" - } - }, - - // ===== PHPUNIT - DEBUG ALL UNIT TESTS ===== - { - "name": "PHPUnit: Debug All Unit Tests", - "type": "php", - "request": "launch", - "program": "${workspaceFolder}/vendor/bin/phpunit", - "args": ["--testsuite=Unit", "--colors=always", "--testdox"], - "cwd": "${workspaceFolder}", - "port": 9003, - "runtimeArgs": ["-dxdebug.mode=debug", "-dxdebug.start_with_request=yes"], - "env": { - "XDEBUG_MODE": "debug" - } - }, - - // ===== PHPUNIT - DEBUG ALL INTEGRATION TESTS ===== - { - "name": "PHPUnit: Debug Integration Tests", - "type": "php", - "request": "launch", - "program": "${workspaceFolder}/vendor/bin/phpunit", - "args": ["--testsuite=Integration", "--colors=always", "--testdox"], - "cwd": "${workspaceFolder}", - "port": 9003, - "runtimeArgs": ["-dxdebug.mode=debug", "-dxdebug.start_with_request=yes"], - "env": { - "XDEBUG_MODE": "debug" - } - }, - - // ===== PHPUNIT - DEBUG ALL TESTS ===== - { - "name": "PHPUnit: Debug All Tests", - "type": "php", - "request": "launch", - "program": "${workspaceFolder}/vendor/bin/phpunit", - "args": ["--colors=always", "--testdox"], - "cwd": "${workspaceFolder}", - "port": 9003, - "runtimeArgs": ["-dxdebug.mode=debug", "-dxdebug.start_with_request=yes"], - "env": { - "XDEBUG_MODE": "debug" - } - }, - - // ===== PHPSTAN - DEBUG ANALYSIS ===== - { - "name": "PHPStan: Debug Analysis", - "type": "php", - "request": "launch", - "program": "${workspaceFolder}/vendor/bin/phpstan", - "args": ["analyse", "src", "--level=max", "--memory-limit=512M"], - "cwd": "${workspaceFolder}", - "port": 9003, - "runtimeArgs": ["-dxdebug.mode=debug", "-dxdebug.start_with_request=yes"] - }, - - // ===== PSALM - DEBUG ANALYSIS ===== - { - "name": "Psalm: Debug Analysis", - "type": "php", - "request": "launch", - "program": "${workspaceFolder}/vendor/bin/psalm", - "args": ["--show-info=true", "--stats"], - "cwd": "${workspaceFolder}", - "port": 9003, - "runtimeArgs": ["-dxdebug.mode=debug", "-dxdebug.start_with_request=yes"] - }, - - // ===== COMPOSER - DEBUG OPERATIONS ===== - { - "name": "Composer: Debug Install", - "type": "php", - "request": "launch", - "program": "/usr/bin/composer", - "args": ["install", "--no-interaction", "-vvv"], - "cwd": "${workspaceFolder}", - "port": 9003, - "runtimeArgs": ["-dxdebug.mode=debug", "-dxdebug.start_with_request=yes"] - }, - - // ===== BENCHMARK - DEBUG PERFORMANCE ===== - { - "name": "PHPBench: Debug Benchmarks", - "type": "php", - "request": "launch", - "program": "${workspaceFolder}/vendor/bin/phpbench", - "args": ["run", "--report=default"], - "cwd": "${workspaceFolder}", - "port": 9003, - "runtimeArgs": ["-dxdebug.mode=debug", "-dxdebug.start_with_request=yes"], - "env": { - "XDEBUG_MODE": "debug" - } - } - ] -} diff --git a/.vscode/php.code-snippets b/.vscode/php.code-snippets deleted file mode 100644 index 45d9915..0000000 --- a/.vscode/php.code-snippets +++ /dev/null @@ -1,405 +0,0 @@ -{ - // ============================================================================ - // KaririCode\Parser - PHP Code Snippets - // ============================================================================ - // Premium documentation snippets following KaririCode standards - // Usage: Type snippet prefix and press Tab - // ============================================================================ - - // ===== CLASS DOCUMENTATION ===== - - "KaririCode Class Documentation": { - "prefix": "kc-class-doc", - "body": [ - "/**", - " * ${1:Brief description of the class}", - " *", - " * ${2:Detailed description explaining:", - " * - What it does", - " * - Why it exists", - " * - How it fits into the ecosystem", - " * - Key design decisions}", - " *", - " * ## ${3:Feature Name} Specification", - " *", - " * ${4:Specification details}", - " *", - " * ## Supported Patterns", - " *", - " * ### 1. ${5:Pattern Name}", - " * ```php", - " * ${6:// Example code}", - " * ```", - " *", - " * ## Performance Characteristics", - " *", - " * - **Parse Speed**: ${7:~XXXns per operation}", - " * - **Memory**: ${8:O(n) where n = ...}", - " * - **Throughput**: ${9:XXX ops/sec}", - " *", - " * ## Algorithm Details", - " *", - " * **Phase 1: ${10:Name}**", - " * 1. ${11:Step description}", - " *", - " * ## Error Handling", - " *", - " * Throws ${12:ExceptionType} for:", - " * - ${13:Condition 1}", - " * - ${14:Condition 2}", - " *", - " * ## Limitations", - " *", - " * - ${15:Limitation 1}", - " * - ${16:Limitation 2}", - " *", - " * ## References", - " *", - " * - ${17:Reference with link}", - " *", - " * @package KaririCode\\\\Parser\\\\${18:Namespace}", - " * @author Walmir Silva ", - " * @see ${19:RelatedClass} For ${20:context}", - " * @link ${21:https://wiki.php.net/rfc/...}", - " * @since ${22:1.0.0}", - " */" - ], - "description": "KaririCode premium class documentation template" - }, - - "KaririCode Method Documentation": { - "prefix": "kc-method-doc", - "body": [ - "/**", - " * ${1:Brief description of what method does}", - " *", - " * ${2:Optional detailed explanation if method is complex}", - " *", - " * **Example 1: ${3:Use Case Name}**", - " * ```php", - " * ${4:// Example code}", - " * // ${5:Expected output/behavior}", - " * ```", - " *", - " * **Example 2: ${6:Another Use Case}**", - " * ```php", - " * ${7:// Example code}", - " * // ${8:Expected output/behavior}", - " * ```", - " *", - " * **Performance**: ${9:~XXXns per operation}", - " *", - " * @param ${10:Type} \\$${11:param} ${12:Description}", - " * @param ${13:Type} \\$${14:param2} ${15:Description}", - " *", - " * @return ${16:ReturnType} ${17:Description}", - " *", - " * @throws ${18:ExceptionType} If ${19:condition}", - " *", - " * @see ${20:RelatedMethod} For ${21:related functionality}", - " */" - ], - "description": "KaririCode method documentation with examples" - }, - - // ===== QUICK DOCUMENTATION ===== - - "Simple PHPDoc Block": { - "prefix": "kc-doc", - "body": [ - "/**", - " * ${1:Description}", - " *", - " * @param ${2:Type} \\$${3:param} ${4:Description}", - " *", - " * @return ${5:Type} ${6:Description}", - " */" - ], - "description": "Simple PHPDoc block" - }, - - "Property Documentation": { - "prefix": "kc-prop-doc", - "body": [ - "/**", - " * ${1:Description}", - " *", - " * @var ${2:Type}", - " */" - ], - "description": "Property documentation" - }, - - // ===== CLASS TEMPLATES ===== - - "KaririCode Class Template": { - "prefix": "kc-class", - "body": [ - "", - " * @since ${4:1.0.0}", - " */", - "final class ${5:ClassName}", - "{", - " ${0}", - "}" - ], - "description": "KaririCode class template" - }, - - "KaririCode Interface Template": { - "prefix": "kc-interface", - "body": [ - "", - " * @since ${4:1.0.0}", - " */", - "interface ${5:InterfaceName}", - "{", - " ${0}", - "}" - ], - "description": "KaririCode interface template" - }, - - "KaririCode Trait Template": { - "prefix": "kc-trait", - "body": [ - "", - " * @since ${4:1.0.0}", - " */", - "trait ${5:TraitName}", - "{", - " ${0}", - "}" - ], - "description": "KaririCode trait template" - }, - - "KaririCode Enum Template": { - "prefix": "kc-enum", - "body": [ - "", - " * @since ${4:1.0.0}", - " */", - "enum ${5:EnumName}: ${6:string}", - "{", - " case ${7:CASE_NAME} = '${8:value}';", - " ${0}", - "}" - ], - "description": "KaririCode enum template" - }, - - // ===== TEST TEMPLATES ===== - - "KaririCode Test Class": { - "prefix": "kc-test", - "body": [ - "${3:instance} = new ${2:ClassName}();", - " }", - "", - " public function test${4:MethodName}(): void", - " {", - " // Arrange", - " ${5:// Setup}", - "", - " // Act", - " ${6:\\$result = \\$this->${3:instance}->${7:method}();}", - "", - " // Assert", - " ${8:\\$this->assertSame(\\$expected, \\$result);}", - " }", - " ${0}", - "}" - ], - "description": "KaririCode test class template" - }, - - "Test Method": { - "prefix": "kc-test-method", - "body": [ - "public function test${1:MethodName}(): void", - "{", - " // Arrange", - " ${2:// Setup}", - "", - " // Act", - " ${3:\\$result = \\$this->instance->${4:method}();}", - "", - " // Assert", - " ${5:\\$this->assertSame(\\$expected, \\$result);}", - "}" - ], - "description": "Test method template" - }, - - // ===== PERFORMANCE ANNOTATIONS ===== - - "Performance Note": { - "prefix": "kc-perf", - "body": [ - "/**", - " * Performance: ~${1:XXX}${2|ns,μs,ms|} per ${3:operation}", - " *", - " * Measured on:", - " * - PHP ${4:8.4.0}", - " * - ${5:CPU info}", - " * - ${6:RAM info}", - " *", - " * Benchmarked using PHPBench with ${7:1000} iterations.", - " */" - ], - "description": "Performance documentation" - }, - - "Algorithm Documentation": { - "prefix": "kc-algo", - "body": [ - "/**", - " * ## Algorithm Details", - " *", - " * **Phase 1: ${1:Name}**", - " * 1. ${2:Step description}", - " * 2. ${3:Step description}", - " *", - " * **Phase 2: ${4:Name}**", - " * 1. ${5:Step description}", - " * 2. ${6:Step description}", - " *", - " * **Complexity**: ${7:O(n)}", - " * **Space**: ${8:O(1)}", - " */" - ], - "description": "Algorithm documentation" - }, - - // ===== COMMON PATTERNS ===== - - "Constructor with Promoted Properties": { - "prefix": "kc-construct", - "body": [ - "/**", - " * ${1:Description}", - " *", - " * @param ${2:Type} \\$${3:property} ${4:Description}", - " */", - "public function __construct(", - " private readonly ${2:Type} \\$${3:property},", - ") {", - " ${0}", - "}" - ], - "description": "Constructor with promoted properties" - }, - - "Getter Method": { - "prefix": "kc-get", - "body": [ - "/**", - " * ${1:Description}", - " *", - " * @return ${2:Type}", - " */", - "public function get${3:Property}(): ${2:Type}", - "{", - " return \\$this->${4:property};", - "}" - ], - "description": "Getter method" - }, - - // ===== EXCEPTION TEMPLATES ===== - - "KaririCode Exception": { - "prefix": "kc-exception", - "body": [ - "", - " * @since ${3:1.0.0}", - " */", - "final class ${4:ExceptionName} extends RuntimeException", - "{", - " ${0}", - "}" - ], - "description": "KaririCode exception class" - } -} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 062a45d..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,155 +0,0 @@ -{ - // ============================================================================ - // KaririCode\Parser - VS Code Workspace Settings - // ============================================================================ - // Professional workspace configuration with AUTO-FORMAT ON SAVE enabled - // Last Updated: 2025-01-27 - // ============================================================================ - - // ===== PHP CONFIGURATION ===== - "php.suggest.basic": false, - "php.validate.enable": true, - "php.validate.executablePath": "/usr/bin/php", - "php.validate.run": "onType", - - // ===== INTELEPHENSE ===== - "intelephense.stubs": [ - "Core", "date", "standard", "pcre", "SPL", "Reflection", - "apache", "bcmath", "bz2", "calendar", "ctype", "curl", "dba", "dom", - "enchant", "exif", "FFI", "fileinfo", "filter", "ftp", "gd", "gettext", - "gmp", "hash", "iconv", "imap", "intl", "json", "ldap", "libxml", - "mbstring", "mysqli", "oci8", "openssl", "pcntl", "PDO", "pdo_mysql", - "pdo_pgsql", "pdo_sqlite", "Phar", "posix", "pspell", "random", - "readline", "session", "shmop", "SimpleXML", "soap", "sockets", - "sodium", "sqlite3", "sysvmsg", "sysvsem", "sysvshm", "tokenizer", - "xml", "xmlreader", "xmlwriter", "xsl", "Zend OPcache", "zip", "zlib" - ], - "intelephense.diagnostics.enable": true, - "intelephense.diagnostics.run": "onType", - "intelephense.diagnostics.undefinedTypes": true, - "intelephense.diagnostics.undefinedFunctions": true, - "intelephense.diagnostics.undefinedConstants": true, - "intelephense.diagnostics.undefinedMethods": true, - "intelephense.diagnostics.undefinedProperties": true, - "intelephense.diagnostics.undefinedVariables": true, - "intelephense.diagnostics.unusedSymbols": true, - "intelephense.completion.maxItems": 100, - "intelephense.format.enable": false, - "intelephense.telemetry.enabled": false, - - // ===== PHP-CS-FIXER (AUTO-FORMAT ENABLED) ===== - "php-cs-fixer.executablePath": "${workspaceFolder}/vendor/bin/php-cs-fixer", - "php-cs-fixer.onsave": true, // ← HABILITADO - "php-cs-fixer.config": "${workspaceFolder}/.php-cs-fixer.php", - "php-cs-fixer.allowRisky": true, - "php-cs-fixer.pathMode": "override", - "php-cs-fixer.exclude": ["vendor"], - "php-cs-fixer.documentFormattingProvider": true, - - // ===== PHPSTAN ===== - "phpstan.enabled": true, - "phpstan.level": "max", - "phpstan.enableStatusBar": true, - "phpstan.enableLanguageServer": true, - "phpstan.path": "${workspaceFolder}/vendor/bin/phpstan", - "phpstan.configFile": "${workspaceFolder}/phpstan.neon", - "phpstan.memoryLimit": "512M", - "phpstan.options": ["--no-progress"], - - // ===== PSALM ===== - "psalm.enabled": true, - "psalm.path": "${workspaceFolder}/vendor/bin/psalm", - "psalm.configPaths": ["${workspaceFolder}/psalm.xml"], - "psalm.unusedVariableDetection": true, - - // ===== PHPCS (ValeryanM.vscode-phpsab) ===== - "phpsab.snifferEnable": true, - "phpsab.fixerEnable": false, - "phpsab.executablePathCS": "${workspaceFolder}/vendor/bin/phpcs", - "phpsab.standard": "Automatic", - "phpsab.autoRulesetSearch": true, - "phpsab.snifferMode": "onSave", - "phpsab.snifferShowSources": true, - - // ===== PHPUNIT ===== - "phpunit.php": "/usr/bin/php", - "phpunit.phpunit": "${workspaceFolder}/vendor/bin/phpunit", - "phpunit.args": ["--colors=always", "--testdox"], - "phpunit.clearOutputOnRun": true, - "phpunit.showAfterExecution": "always", - - // ==== DOCS ===== - "xml.fileAssociations": [ - { - "systemId": "https://raw.githubusercontent.com/phpDocumentor/phpDocumentor/master/data/xsd/phpdoc.xsd", // The schema URL from the docs - "pattern": "**/phpdoc.dist.xml" // Apply this schema to these files - } - ], - - // ===== EDITOR - GENERAL ===== - "editor.formatOnSave": true, // ← HABILITADO GLOBALMENTE - "editor.formatOnPaste": false, - "editor.formatOnType": false, - "editor.tabSize": 4, - "editor.insertSpaces": true, - "editor.detectIndentation": false, - "editor.trimAutoWhitespace": false, - "editor.rulers": [80, 120], - "editor.wordWrap": "off", - "editor.renderWhitespace": "boundary", - "editor.bracketPairColorization.enabled": true, - "editor.guides.bracketPairs": true, - "editor.minimap.enabled": true, - - // ===== EDITOR - PHP SPECIFIC ===== - "[php]": { - "editor.defaultFormatter": "junstyle.php-cs-fixer", // ← USAR PHP-CS-FIXER - "editor.formatOnSave": true, // ← HABILITADO PARA PHP - "editor.tabSize": 4, - "editor.insertSpaces": true, - "editor.rulers": [80, 120, 180], - "editor.codeActionsOnSave": { - "source.organizeImports": "never", - "source.fixAll": "never" - } - }, - - // ===== FILES ===== - "files.encoding": "utf8", - "files.eol": "\n", - "files.insertFinalNewline": true, - "files.trimFinalNewlines": true, - "files.trimTrailingWhitespace": false, - "files.autoSave": "onFocusChange", - "files.associations": { - "*.php": "php", - "*.neon": "yaml", - ".php-cs-fixer.php": "php", - "phpstan.neon": "yaml", - "psalm.xml": "xml", - "phpunit.xml": "xml", - "phpcs.xml": "xml" - }, - "files.exclude": { - "**/.git": true, - "**/.DS_Store": true, - "**/vendor": false, - "**/.phpunit.cache": true, - "**/.php-cs-fixer.cache": true - }, - - // ===== TODO TREE ===== - "todo-tree.general.tags": ["TODO", "FIXME", "BUG", "HACK", "NOTE", "OPTIMIZE", "CRITICAL"], - "todo-tree.highlights.customHighlight": { - "TODO": {"icon": "check", "foreground": "#000", "background": "#ffbd2e", "iconColour": "#ffbd2e"}, - "FIXME": {"icon": "alert", "foreground": "#fff", "background": "#f06292", "iconColour": "#f06292"}, - "CRITICAL": {"icon": "flame", "foreground": "#fff", "background": "#f00", "iconColour": "#f00"}, - "BUG": {"icon": "bug", "foreground": "#fff", "background": "#ff6347", "iconColour": "#ff6347"}, - "OPTIMIZE": {"icon": "zap", "foreground": "#000", "background": "#64dd17", "iconColour": "#64dd17"} - }, - - // ===== OTHER ===== - "prettier.enable": false, - "git.autofetch": true, - "workbench.colorTheme": "Default Dark Modern" -} diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 0c89b13..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,490 +0,0 @@ -{ - // ============================================================================ - // KaririCode\Parser - VS Code Tasks (Makefile Integration) - // ============================================================================ - // Integrates Makefile targets with VS Code task runner - // Access via: Ctrl+Shift+B or Terminal > Run Task - // ============================================================================ - - "version": "2.0.0", - "tasks": [ - // ===== SETUP & INSTALLATION ===== - { - "label": "Install Dependencies", - "type": "shell", - "command": "make install", - "group": "build", - "presentation": { - "echo": true, - "reveal": "always", - "focus": false, - "panel": "shared", - "showReuseMessage": true, - "clear": false - }, - "problemMatcher": [] - }, - { - "label": "Update Dependencies", - "type": "shell", - "command": "make update", - "group": "build", - "presentation": { - "reveal": "always", - "panel": "shared" - }, - "problemMatcher": [] - }, - - // ===== TESTING ===== - { - "label": "Run All Tests", - "type": "shell", - "command": "make test", - "group": { - "kind": "test", - "isDefault": true - }, - "presentation": { - "echo": true, - "reveal": "always", - "focus": true, - "panel": "shared", - "showReuseMessage": false, - "clear": true - }, - "problemMatcher": [] - }, - { - "label": "Run Unit Tests", - "type": "shell", - "command": "make test-unit", - "group": "test", - "presentation": { - "reveal": "always", - "focus": true, - "panel": "shared", - "clear": true - }, - "problemMatcher": [] - }, - { - "label": "Run Integration Tests", - "type": "shell", - "command": "make test-integration", - "group": "test", - "presentation": { - "reveal": "always", - "panel": "shared" - }, - "problemMatcher": [] - }, - { - "label": "Generate Coverage Report", - "type": "shell", - "command": "make coverage", - "group": "test", - "presentation": { - "reveal": "always", - "panel": "shared" - }, - "problemMatcher": [] - }, - { - "label": "Open Coverage Report", - "type": "shell", - "command": "xdg-open coverage/html/index.html || open coverage/html/index.html", - "group": "test", - "presentation": { - "reveal": "never" - }, - "problemMatcher": [] - }, - { - "label": "Run Mutation Tests", - "type": "shell", - "command": "make mutation", - "group": "test", - "presentation": { - "reveal": "always", - "panel": "shared" - }, - "problemMatcher": [] - }, - - // ===== STATIC ANALYSIS ===== - { - "label": "Run All Analysis", - "type": "shell", - "command": "make analyse", - "group": "build", - "presentation": { - "echo": true, - "reveal": "always", - "focus": true, - "panel": "shared", - "showReuseMessage": false, - "clear": true - }, - "problemMatcher": [ - "$phpcs", - { - "owner": "phpstan", - "fileLocation": ["relative", "${workspaceFolder}"], - "pattern": { - "regexp": "^\\s*(.*):(\\d+)\\s*(.*)$", - "file": 1, - "line": 2, - "message": 3 - } - } - ] - }, - { - "label": "Run PHPStan", - "type": "shell", - "command": "make phpstan", - "group": "build", - "presentation": { - "reveal": "always", - "focus": true, - "panel": "shared", - "clear": true - }, - "problemMatcher": [] - }, - { - "label": "Run Psalm", - "type": "shell", - "command": "make psalm", - "group": "build", - "presentation": { - "reveal": "always", - "panel": "shared" - }, - "problemMatcher": [] - }, - { - "label": "Run Psalm Taint Analysis", - "type": "shell", - "command": "make psalm-taint", - "group": "build", - "presentation": { - "reveal": "always", - "panel": "shared" - }, - "problemMatcher": [] - }, - - // ===== CODE STYLE ===== - { - "label": "Check Coding Standards", - "type": "shell", - "command": "make cs-check", - "group": "build", - "presentation": { - "reveal": "always", - "panel": "shared" - }, - "problemMatcher": ["$phpcs"] - }, - { - "label": "Fix Coding Standards", - "type": "shell", - "command": "make cs-fix", - "group": "build", - "presentation": { - "reveal": "always", - "panel": "shared" - }, - "problemMatcher": [] - }, - { - "label": "Format Code (PHP-CS-Fixer)", - "type": "shell", - "command": "make format", - "group": "build", - "presentation": { - "echo": true, - "reveal": "always", - "focus": false, - "panel": "shared", - "showReuseMessage": false - }, - "problemMatcher": [] - }, - { - "label": "Format Code (Dry Run)", - "type": "shell", - "command": "make format-dry", - "group": "build", - "presentation": { - "reveal": "always", - "panel": "shared" - }, - "problemMatcher": [] - }, - { - "label": "Lint PHP Files", - "type": "shell", - "command": "make lint", - "group": "build", - "presentation": { - "reveal": "always", - "panel": "shared" - }, - "problemMatcher": [] - }, - - // ===== QUALITY CHECKS ===== - { - "label": "Run All Quality Checks", - "type": "shell", - "command": "make check", - "group": { - "kind": "build", - "isDefault": true - }, - "presentation": { - "echo": true, - "reveal": "always", - "focus": true, - "panel": "shared", - "showReuseMessage": false, - "clear": true - }, - "problemMatcher": [] - }, - { - "label": "Validate Composer", - "type": "shell", - "command": "make validate", - "group": "build", - "presentation": { - "reveal": "always", - "panel": "shared" - }, - "problemMatcher": [] - }, - { - "label": "Security Audit", - "type": "shell", - "command": "make security", - "group": "build", - "presentation": { - "reveal": "always", - "panel": "shared" - }, - "problemMatcher": [] - }, - { - "label": "Check Outdated Dependencies", - "type": "shell", - "command": "make outdated", - "group": "build", - "presentation": { - "reveal": "always", - "panel": "shared" - }, - "problemMatcher": [] - }, - - // ===== CI/CD ===== - { - "label": "Run CI Pipeline", - "type": "shell", - "command": "make ci", - "group": "build", - "presentation": { - "echo": true, - "reveal": "always", - "focus": true, - "panel": "dedicated", - "showReuseMessage": false, - "clear": true - }, - "problemMatcher": [] - }, - { - "label": "Run Full CI Pipeline", - "type": "shell", - "command": "make ci-full", - "group": "build", - "presentation": { - "reveal": "always", - "focus": true, - "panel": "dedicated", - "clear": true - }, - "problemMatcher": [] - }, - { - "label": "Run CD Pipeline", - "type": "shell", - "command": "make cd", - "group": "build", - "presentation": { - "reveal": "always", - "panel": "dedicated" - }, - "problemMatcher": [] - }, - - // ===== BENCHMARKING ===== - { - "label": "Run Benchmarks", - "type": "shell", - "command": "make benchmark", - "group": "test", - "presentation": { - "reveal": "always", - "panel": "shared" - }, - "problemMatcher": [] - }, - { - "label": "Compare Benchmarks", - "type": "shell", - "command": "make benchmark-compare", - "group": "test", - "presentation": { - "reveal": "always", - "panel": "shared" - }, - "problemMatcher": [] - }, - - // ===== CLEANUP ===== - { - "label": "Clean Build Artifacts", - "type": "shell", - "command": "make clean", - "group": "build", - "presentation": { - "reveal": "always", - "panel": "shared" - }, - "problemMatcher": [] - }, - { - "label": "Deep Clean (including vendor)", - "type": "shell", - "command": "make clean-all", - "group": "build", - "presentation": { - "reveal": "always", - "panel": "shared" - }, - "problemMatcher": [] - }, - - // ===== DOCKER TASKS ===== - { - "label": "Docker: Pull Image", - "type": "shell", - "command": "make docker-pull", - "group": "build", - "presentation": { - "reveal": "always", - "panel": "shared" - }, - "problemMatcher": [] - }, - { - "label": "Docker: Run Tests", - "type": "shell", - "command": "make docker-test", - "group": "test", - "presentation": { - "reveal": "always", - "panel": "shared" - }, - "problemMatcher": [] - }, - { - "label": "Docker: Run CI", - "type": "shell", - "command": "make docker-ci", - "group": "build", - "presentation": { - "reveal": "always", - "panel": "dedicated" - }, - "problemMatcher": [] - }, - { - "label": "Docker: Open Shell", - "type": "shell", - "command": "make docker-shell", - "group": "none", - "isBackground": false, - "presentation": { - "reveal": "always", - "panel": "dedicated", - "focus": true - }, - "problemMatcher": [] - }, - - // ===== UTILITIES ===== - { - "label": "Show Project Stats", - "type": "shell", - "command": "make stats", - "group": "none", - "presentation": { - "reveal": "always", - "panel": "shared" - }, - "problemMatcher": [] - }, - { - "label": "Show Project Info", - "type": "shell", - "command": "make info", - "group": "none", - "presentation": { - "reveal": "always", - "panel": "shared" - }, - "problemMatcher": [] - }, - { - "label": "Count Lines of Code", - "type": "shell", - "command": "make loc", - "group": "none", - "presentation": { - "reveal": "always", - "panel": "shared" - }, - "problemMatcher": [] - }, - - // ===== PRE-COMMIT ===== - { - "label": "Run Pre-Commit Checks", - "type": "shell", - "command": "make pre-commit", - "group": "build", - "presentation": { - "reveal": "always", - "focus": true, - "panel": "shared", - "clear": true - }, - "problemMatcher": [] - }, - { - "label": "Install Git Hooks", - "type": "shell", - "command": "make install-hooks", - "group": "build", - "presentation": { - "reveal": "always", - "panel": "shared" - }, - "problemMatcher": [] - } - ] -} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1154e38 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,74 @@ +# Changelog + +All notable changes to **KaririCode Devkit** are documented here. + +The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +--- + +## [Unreleased] + +### Added + +- **`bin/build-phar.php`:** Native PHP PHAR builder — compiles `kcode.phar` without `humbug/box`. Added as a workaround for a Box 4.x / PHP 8.4 incompatibility (`chdir(): Not a directory` during `endBuffering()`). +- **Test suite — `tests/Unit/Exception`:** `DevkitExceptionTest`, `ConfigurationExceptionTest`, `ToolExceptionTest`. +- **Test suite — `tests/Unit/ValueObject`:** `ToolResultTest`, `QualityReportTest`, `MigrationReportTest`. +- **Test suite — `tests/Unit/Core`:** `ProjectContextTest`, `DevkitConfigTest`. +- **Full unit test suite:** 41 tests, 81 assertions — all passing on PHP 8.4 / PHPUnit 12.4.5. + +### Changed + +- **`composer.json`:** `phpunit/phpunit` constraint updated from `^11.0` to `^12.0`. Composer scripts renamed to `kcode:*` prefix to avoid collision with built-in Composer commands. `infection/extension-installer: false` added to `allow-plugins` to suppress spurious warnings. The `build` script now calls `bin/build-phar.php` instead of `box compile`. +- **`Makefile`:** `build` target no longer requires `humbug/box` — uses `PHAR_BUILDER := bin/build-phar.php`. `VERSION` simplified to `git describe --tags` only (no `box.json` fallback). `check-env` shows PHAR builder status instead of Box version. `_require-box` guard replaced by `_require-phar-builder`. `install`/`install-prod` add `--no-scripts`. New `security` target. New `_require-kcode` guard on all quality targets. `distclean` no longer removes `composer.lock`. `check-env` correctly detects `vendor/bin/kcode`. +- **`.github/workflows/ci.yml`:** Added `develop` branch to push/PR triggers. +- **`.github/workflows/code-quality.yml`:** Replaced 378-line legacy workflow (PHPMD + runtime `composer require`) with a lean 160-line workflow aligned to the kcode CLI. Jobs: `dependencies → security → phpstan → cs-fixer → quality-summary`. +- **`.gitignore`:** Expanded from 8 to 42 patterns (legacy scaffold files, IDE artefacts, OS artefacts, environment files, tool caches). +- **`docs/BUILDING.md`:** Fully rewritten for `bin/build-phar.php` — removed all `humbug/box` references. +- **`phpunit.xml`:** `memory_limit` reduced from 1G to 256M. Attributes reformatted one-per-line. + +### Fixed + +- **`bin/kcode`:** Fatal `TypeError` when invoked via Composer scripts — `$argv` was inaccessible inside the static IIFE. Fixed by passing it as an explicit parameter (`$argv ?? []`). +- **`src/Configuration/PhpStanConfigGenerator`:** Removed `checkMissingIterableValueType` and `checkGenericClassInNonGenericObjectType` — both were removed from PHPStan 2.x core and caused `Invalid configuration` errors. +- **`src/Configuration/CsFixerConfigGenerator`:** Added `(string)` cast on `preg_replace()` return values in `exportRules()` to satisfy PHPStan level-9 type constraints. +- **`src/Core/Devkit`:** Added `false` guard on `file_get_contents()` in `appendGitignore()`. Added `@var \SplFileInfo` annotation in `removeRecursive()`. +- **`src/Core/DevkitConfig`:** Added `@var array` before assigning loaded overrides. `toolVersions()` now correctly returns `array` via `array_filter`. +- **`src/Core/MigrationDetector`:** Added `false !== $raw` guard on `file_get_contents()`. Added `@var` annotations on `json_decode()` result. +- **`src/Core/ProjectDetector`:** Added `false !== $raw` guard on `file_get_contents()`. Extracted `$psr4Source`, `$psr4Test`, `$projectName` with proper `is_array()` guards. Fixed `detectNamespace()` and `detectPhpVersion()` with nested `is_array()` / `is_string()` checks. +- **`src/Runner/*`:** Added `#[\Override]` attribute to `toolName()`, `vendorBin()`, and `defaultArguments()` across all six runners. +- **`src/ValueObject/MigrationReport`:** Added `false` guard after `file_get_contents()` and `json_encode()`. Extracted `$requireDev` with `@var` and `is_array()` guard. Fixed `removePackagesFromComposer()` to write back the updated `$requireDev` slice into `$composer` before `json_encode()`. Added `@var \SplFileInfo` in `removeRecursive()`. +- **Security:** `phpunit/phpunit` updated from 12.4.2 → 12.4.5 (fixes CVE-2026-24765, HIGH). + +### Removed + +- **`humbug/box`:** Removed as a build dependency. `bin/build-phar.php` replaces it. +- **Legacy scaffold files (42):** `.config/`, `.docs/`, `.make/`, `devkit/`, `docker-compose.yml`, `.env.example`, `.env.xdebug`, root-level tool configs, `build/`, `coverage/`. +- **Prototype test files (4):** Targeting removed classes `UserProfile`, `Email`, `UserId`, `UserRole`. + +**Net result:** PHPStan level 9 — **0 errors** (down from 35). PHPUnit — **41 tests, 81 assertions passing**. + +--- + +## [1.0.0] — 2025-12-01 + +### Added + +- **Core:** `Devkit` orchestrator, `ProjectDetector`, `ProjectContext`, `DevkitConfig`, `ProcessExecutor`. +- **Contracts:** `ConfigGenerator` and `ToolRunner` interfaces. +- **Configuration generators:** PHPUnit, PHPStan, PHP-CS-Fixer, Rector, Psalm. +- **Tool runners:** PHPUnit, PHPStan, PHP-CS-Fixer, Rector, Psalm, Composer Audit. +- **CLI commands:** `init`, `migrate`, `test`, `analyse`, `cs:fix`, `rector`, `security`, `quality`, `format`, `clean`. +- **Migration detector:** Scans `composer.json` require-dev and project root for redundant files and cache paths. Interactive cleanup with `--dry-run` and `--no-interaction`. +- **CLI framework:** Zero-dependency `Application` router with ANSI output, argument parsing, and passthrough support. +- **Value objects:** `ToolResult`, `QualityReport`, `MigrationReport` (immutable, readonly). +- **Exception hierarchy:** `DevkitException`, `ConfigurationException`, `ToolException`. +- **Build automation:** `Makefile` with targets: `install`, `build`, `verify`, `self-test`, `quality`, `security`, `clean`, `release`. +- **CI/CD:** GitHub Actions workflows for quality checks (`ci.yml`) and automated PHAR releases (`release.yml`). +- **Binary resolution:** Three-tier strategy — PHAR → vendor → global PATH. +- **Override system:** `devkit.php` at project root with type-safe merging. Scaffold with `kcode init --config`. +- **Documentation:** README, 6 ADRs, 3 specifications, docs index. + +--- + +[Unreleased]: https://github.com/kariricode/devkit/compare/v1.0.0...HEAD +[1.0.0]: https://github.com/kariricode/devkit/releases/tag/v1.0.0 diff --git a/LICENSE b/LICENSE index 591ed74..70fd988 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 KaririCode Framework +Copyright (c) 2025 Walmir Silva / KaririCode Framework Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index 6face23..4fe2430 100644 --- a/Makefile +++ b/Makefile @@ -1,90 +1,261 @@ -# ============================================================================== -# KaririCode\DevKit - Professional Development Makefile -# ============================================================================== +# ╔══════════════════════════════════════════════════════════════╗ +# ║ KaririCode Devkit — Build & Quality Automation ║ +# ║ https://github.com/kariricode/devkit ║ +# ╚══════════════════════════════════════════════════════════════╝ # -# Makefile modular seguindo SOLID principles e DRY -# Organização semântica por responsabilidade +# make Show available targets +# make quality Run full quality pipeline via kcode +# make release Full release: quality → build → verify +# make build Compile kcode.phar # -# Usage: -# make -# make help - Display all available targets +# Requirements: +# PHP ≥ 8.4 · Composer 2.x # -# Author: Walmir Silva -# URL: https:\/\/github.com/KaririCode-Framework/kariricode-devkit -# ============================================================================== + +.PHONY: help install install-prod build verify self-test \ + quality test analyse cs-check cs-fix rector format security lint \ + clean distclean release check-env .DEFAULT_GOAL := help -.PHONY: help - -# ============================================================================== -# CORE INCLUDES (ordem de dependência) -# ============================================================================== - -MAKE_DIR := .make - -# 1. Core - Variáveis e funções compartilhadas --include $(MAKE_DIR)/core/Makefile.variables.mk --include $(MAKE_DIR)/core/Makefile.functions.mk - -# 2. Local - Targets locais --include $(MAKE_DIR)/local/Makefile.setup.mk --include $(MAKE_DIR)/local/Makefile.qa.mk --include $(MAKE_DIR)/local/Makefile.helpers.mk - -# 3. Pipeline - Orquestração --include $(MAKE_DIR)/pipeline/Makefile.orchestration.mk - -# 4. Docker - Targets Docker --include $(MAKE_DIR)/docker/Makefile.docker-core.mk --include $(MAKE_DIR)/docker/Makefile.docker-compose.mk --include $(MAKE_DIR)/docker/Makefile.docker-qa.mk --include $(MAKE_DIR)/docker/Makefile.docker-image.mk --include $(MAKE_DIR)/docker/Makefile.docker-tools.mk - -# ============================================================================== -# HELP SYSTEM -# ============================================================================== - -define AWK_HELP_SCRIPT -BEGIN { \ - FS = ":.*?## "; \ - header_printed = 0; \ -} \ -/^[a-zA-Z0-9_-]+:.*?## / { \ - if (header_printed == 0) { \ - printf "\n$(BOLD)%s$(RESET)\n", TITLE; \ - header_printed = 1; \ - } \ - printf " $(CYAN)%-20s$(RESET) %s\n", $$1, $$2; \ -} +SHELL := /bin/bash + +# ── Configuration ────────────────────────────────────────── + +PHP ?= php +COMPOSER ?= composer +KCODE := vendor/bin/kcode +PHAR_BUILDER := bin/build-phar.php + +BUILD_DIR := build +PHAR := $(BUILD_DIR)/kcode.phar + +# Version: prefer git tag, then 'dev' +VERSION := $(shell git describe --tags --abbrev=0 2>/dev/null || echo "dev") +COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown") +TIMESTAMP := $(shell date -u +"%Y-%m-%dT%H:%M:%SZ") + +# ── Colors ───────────────────────────────────────────────── + +_RESET := \033[0m +_BOLD := \033[1m +_DIM := \033[2m +_GREEN := \033[32m +_YELLOW := \033[33m +_CYAN := \033[36m +_RED := \033[31m +_RULE := ────────────────────────────────────────────────── + +define _header + @printf "\n$(_CYAN)$(_RULE)$(_RESET)\n" + @printf " $(_BOLD)$(1)$(_RESET)\n" + @printf "$(_CYAN)$(_RULE)$(_RESET)\n\n" +endef + +define _ok + @printf " $(_GREEN)✓$(_RESET) $(1)\n" +endef + +define _warn + @printf " $(_YELLOW)⚠$(_RESET) $(1)\n" endef -help: ## Display this help message - @echo "" - @echo -e "$(BOLD)$(CYAN)KaririCode\\DevKit - Development Makefile$(RESET)" - @echo -e "$(BLUE)═══════════════════════════════════════════════════════$(RESET)" - - @awk -v TITLE="🚀 Main Pipeline" '$(AWK_HELP_SCRIPT)' $(MAKE_DIR)/pipeline/Makefile.orchestration.mk - @awk -v TITLE="🛠️ Setup & Maintenance" '$(AWK_HELP_SCRIPT)' $(MAKE_DIR)/local/Makefile.setup.mk - @awk -v TITLE="🧪 Quality Assurance (Local)" '$(AWK_HELP_SCRIPT)' $(MAKE_DIR)/local/Makefile.qa.mk - @awk -v TITLE="🧰 Developer Helpers" '$(AWK_HELP_SCRIPT)' $(MAKE_DIR)/local/Makefile.helpers.mk - @awk -v TITLE="🐳 Docker QA Pipeline" '$(AWK_HELP_SCRIPT)' $(MAKE_DIR)/docker/Makefile.docker-qa.mk - @awk -v TITLE="🐳 Docker Compose" '$(AWK_HELP_SCRIPT)' $(MAKE_DIR)/docker/Makefile.docker-compose.mk - @awk -v TITLE="🐳 Docker Core" '$(AWK_HELP_SCRIPT)' $(MAKE_DIR)/docker/Makefile.docker-core.mk - @awk -v TITLE="🐳 Docker Tools" '$(AWK_HELP_SCRIPT)' $(MAKE_DIR)/docker/Makefile.docker-tools.mk - @awk -v TITLE="🐳 Docker Image" '$(AWK_HELP_SCRIPT)' $(MAKE_DIR)/docker/Makefile.docker-image.mk - - @echo "" - @echo -e "$(BOLD)Usage Examples:$(RESET)" - @echo -e " $(YELLOW)make install$(RESET) # Install all dependencies" - @echo -e " $(YELLOW)make ci$(RESET) # Run local CI pipeline" - @echo -e " $(YELLOW)make docker-ci$(RESET) # Run CI in Docker (isolated)" - @echo -e " $(YELLOW)make docker-shell$(RESET) # Open interactive Docker shell" - @echo "" - -debug-composer: ## Debug composer configuration - @echo "COMPOSER_BIN = '$(COMPOSER_BIN)'" - @echo "COMPOSER = '$(COMPOSER)'" - @command -v composer || echo "Composer not found with command -v" - @which composer || echo "Composer not found with which" - @type composer || echo "Composer not found with type" +define _fail + @printf " $(_RED)✗$(_RESET) $(1)\n" +endef + +define _info + @printf " $(_DIM)$(1)$(_RESET)\n" +endef + +# ══════════════════════════════════════════════════════════════ +# Help +# ══════════════════════════════════════════════════════════════ + +help: ## Show available targets + @printf "\n" + @printf " $(_BOLD)KaririCode Devkit$(_RESET) v$(VERSION) $(_DIM)($(COMMIT))$(_RESET)\n" + @printf " $(_DIM)Unified quality toolchain for KaririCode Framework$(_RESET)\n" + @printf "\n" + @printf " $(_YELLOW)Dependencies & Build$(_RESET)\n" + @grep -E '^(install|install-prod|build|verify|self-test|check-env|clean|distclean|release):.*?## ' $(MAKEFILE_LIST) | \ + awk 'BEGIN {FS = ":.*?## "}; {printf " $(_GREEN)%-16s$(_RESET) %s\n", $$1, $$2}' + @printf "\n" + @printf " $(_YELLOW)Quality & Toolchain$(_RESET)\n" + @grep -E '^(quality|test|analyse|cs-check|cs-fix|rector|format|security|lint):.*?## ' $(MAKEFILE_LIST) | \ + awk 'BEGIN {FS = ":.*?## "}; {printf " $(_GREEN)%-16s$(_RESET) %s\n", $$1, $$2}' + @printf "\n" + @printf " $(_DIM)Override defaults: PHP=php8.4 COMPOSER=composer2 make build$(_RESET)\n" + @printf " $(_DIM)Pass extra args: make test ARGS=\"--filter=testFoo\"$(_RESET)\n" + @printf "\n" + +# ══════════════════════════════════════════════════════════════ +# Dependencies +# ══════════════════════════════════════════════════════════════ + +install: ## Install all Composer dependencies (dev + prod) + $(call _header,Installing dependencies) + @$(COMPOSER) install --no-interaction --prefer-dist --optimize-autoloader --no-scripts + $(call _ok,Dependencies installed) + +install-prod: ## Install without dev deps (for PHAR compilation) + $(call _header,Installing production dependencies) + @$(COMPOSER) install --no-dev --no-interaction --prefer-dist --optimize-autoloader --no-scripts + $(call _ok,Production dependencies installed) + +# ══════════════════════════════════════════════════════════════ +# Quality (via kcode CLI) +# ══════════════════════════════════════════════════════════════ + +quality: | _require-vendor _require-kcode ## Full pipeline: cs:check → analyse → test (via kcode quality) + $(call _header,Quality Pipeline) + @$(KCODE) init + @$(KCODE) quality + +test: | _require-vendor _require-kcode ## Run PHPUnit tests + @$(KCODE) test $(ARGS) + +analyse: | _require-vendor _require-kcode ## Run static analysis (PHPStan + Psalm) + @$(KCODE) analyse $(ARGS) + +cs-check: | _require-vendor _require-kcode ## Check code style — dry run (no files changed) + @$(KCODE) cs:fix --check $(ARGS) + +cs-fix: | _require-vendor _require-kcode ## Fix code style with PHP-CS-Fixer + @$(KCODE) cs:fix $(ARGS) + +rector: | _require-vendor _require-kcode ## Run Rector in dry-run mode (use ARGS=--fix to apply) + @$(KCODE) rector $(ARGS) + +format: | _require-vendor _require-kcode ## Apply all formatting: cs:fix + rector --fix + @$(KCODE) format $(ARGS) + +security: | _require-vendor _require-kcode ## Run composer audit for known vulnerabilities + @$(KCODE) security + +lint: cs-check analyse ## Lint: code style check + static analysis (no fixes applied) + +# ══════════════════════════════════════════════════════════════ +# Build (PHAR) +# ══════════════════════════════════════════════════════════════ + +build: | _require-vendor ## Compile kcode.phar via bin/build-phar.php + $(call _header,Building kcode.phar v$(VERSION)) + @mkdir -p $(BUILD_DIR) + @START=$$(date +%s%N); \ + $(PHP) -d phar.readonly=0 $(PHAR_BUILDER) && \ + END=$$(date +%s%N); \ + ELAPSED=$$(( (END - START) / 1000000 )); \ + SECS=$$(( ELAPSED / 1000 )); \ + MS=$$(( ELAPSED % 1000 )); \ + printf "\n"; \ + printf " $(_GREEN)✓$(_RESET) PHAR compiled: $(_BOLD)$(PHAR)$(_RESET)\n"; \ + printf " $(_GREEN)✓$(_RESET) Size: $$(du -h $(PHAR) | cut -f1)\n"; \ + printf " $(_GREEN)✓$(_RESET) Built in $${SECS}.$${MS}s\n"; \ + printf " $(_DIM) Version: $(VERSION) Commit: $(COMMIT) Time: $(TIMESTAMP)$(_RESET)\n" + +# ══════════════════════════════════════════════════════════════ +# Verification +# ══════════════════════════════════════════════════════════════ + +verify: | _require-phar ## Verify PHAR integrity and smoke-test all commands + $(call _header,Verifying kcode.phar) + @PASS=0; FAIL=0; \ + \ + printf " $(_BOLD)Signature$(_RESET)\n"; \ + $(PHP) $(PHAR) --version > /dev/null 2>&1 \ + && { printf " $(_GREEN)✓$(_RESET) --version\n"; PASS=$$((PASS+1)); } \ + || { printf " $(_RED)✗$(_RESET) --version\n"; FAIL=$$((FAIL+1)); }; \ + \ + printf "\n $(_BOLD)Commands$(_RESET)\n"; \ + for cmd in init migrate test analyse cs:fix rector security quality format clean; do \ + $(PHP) $(PHAR) --help 2>/dev/null | grep -q "$$cmd" \ + && { printf " $(_GREEN)✓$(_RESET) $$cmd\n"; PASS=$$((PASS+1)); } \ + || { printf " $(_RED)✗$(_RESET) $$cmd\n"; FAIL=$$((FAIL+1)); }; \ + done; \ + \ + printf "\n"; \ + if [ $$FAIL -eq 0 ]; then \ + printf " $(_GREEN)✓ All $$PASS checks passed$(_RESET)\n"; \ + else \ + printf " $(_RED)✗ $$FAIL of $$((PASS+FAIL)) checks failed$(_RESET)\n"; \ + exit 1; \ + fi + +self-test: | _require-phar ## Run kcode.phar against this project (init + migrate dry-run) + $(call _header,Self-test — kcode.phar on devkit project) + @$(PHP) $(PHAR) init + @$(PHP) $(PHAR) migrate --dry-run + $(call _ok,Self-test passed) + +# ══════════════════════════════════════════════════════════════ +# Clean +# ══════════════════════════════════════════════════════════════ + +clean: ## Remove build artefacts (build/ and .kcode/build/) + $(call _header,Cleaning) + @rm -rf $(BUILD_DIR) + @rm -rf .kcode/build + $(call _ok,Build artefacts removed) + +distclean: clean ## Full clean: artefacts + vendor (for a clean composer install) + @rm -rf vendor + $(call _ok,Vendor removed — run 'make install' to restore) + +# ══════════════════════════════════════════════════════════════ +# Release +# ══════════════════════════════════════════════════════════════ + +release: quality build verify ## Full release pipeline: quality → build → verify + @printf "\n" + @printf " $(_GREEN)═══════════════════════════════════════════════════$(_RESET)\n" + @printf " $(_GREEN) kcode.phar v$(VERSION) ready for release $(_RESET)\n" + @printf " $(_GREEN)═══════════════════════════════════════════════════$(_RESET)\n" + @printf "\n" + @printf " $(_BOLD)Artefact$(_RESET) $(PHAR)\n" + @printf " $(_BOLD)Size$(_RESET) $$(du -h $(PHAR) | cut -f1)\n" + @printf " $(_BOLD)Commit$(_RESET) $(COMMIT)\n" + @printf " $(_BOLD)Time$(_RESET) $(TIMESTAMP)\n" + @printf "\n" + @printf " $(_DIM)Tag and push to trigger GitHub Release:$(_RESET)\n" + @printf " $(_CYAN)git tag v$(VERSION) && git push --tags$(_RESET)\n" + @printf "\n" + +# ══════════════════════════════════════════════════════════════ +# Diagnostics +# ══════════════════════════════════════════════════════════════ + +check-env: ## Show build environment info (PHP, Composer, Box, Git, phar.readonly) + $(call _header,Build Environment) + @printf " $(_BOLD)PHP$(_RESET) $$($(PHP) -v 2>/dev/null | head -1 || echo 'not found')\n" + @printf " $(_BOLD)Composer$(_RESET) "; $(COMPOSER) --version 2>/dev/null | head -1 || printf "not found\n" + @printf " $(_BOLD)PHAR builder$(_RESET) "; test -f $(PHAR_BUILDER) && printf "bin/build-phar.php (OK)\n" || printf "not found\n" + @printf " $(_BOLD)kcode$(_RESET) "; test -f $(KCODE) && $(PHP) $(KCODE) --version 2>/dev/null || printf "not found — run make install\n" + @printf " $(_BOLD)Git tag$(_RESET) $(VERSION) ($(COMMIT))\n" + @printf " $(_BOLD)phar.readonly$(_RESET) $$($(PHP) -r 'echo ini_get("phar.readonly") ? "On (WARN: use php -d phar.readonly=0 for builds)" : "Off (OK)";' 2>/dev/null)\n" + @printf "\n" + +# ── Guards ───────────────────────────────────────────────── + +_require-phar-builder: + @test -f $(PHAR_BUILDER) || { \ + printf "\n $(_RED)✗$(_RESET) $(_BOLD)$(PHAR_BUILDER)$(_RESET) not found. Expected at bin/build-phar.php.\n\n"; \ + exit 1; \ + } + +_require-vendor: + @test -d vendor || { \ + printf "\n $(_RED)✗$(_RESET) vendor/ not found. Run $(_BOLD)make install$(_RESET) first.\n\n"; \ + exit 1; \ + } + +_require-kcode: + @test -f $(KCODE) || { \ + printf "\n $(_RED)✗$(_RESET) $(KCODE) not found. Run $(_BOLD)make install$(_RESET) first.\n\n"; \ + exit 1; \ + } + +_require-phar: + @test -f $(PHAR) || { \ + printf "\n $(_RED)✗$(_RESET) $(PHAR) not found. Run $(_BOLD)make build$(_RESET) first.\n\n"; \ + exit 1; \ + } diff --git a/README.md b/README.md index 1e73b57..4cd9066 100644 --- a/README.md +++ b/README.md @@ -1,640 +1,533 @@ -# 🚀 KaririCode DevKit +# KaririCode Devkit -[![PHP Version](https://img.shields.io/badge/PHP-8.4%2B-blue)](https://www.php.net) -[![License](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE) -[![Code Quality](https://img.shields.io/badge/Code%20Quality-Level%20Max-brightgreen)](phpstan.neon) -[![Docker](https://img.shields.io/badge/Docker-Supported-blue)](docker-compose.yml) -[![Automated Setup](https://img.shields.io/badge/Setup-Automated-success)](install.sh) +
-> **Professional development environment for KaririCode Framework components** -> Fully automated installation, standardized tooling, and production-ready setup for PHP 8.4+ projects +[![PHP 8.4+](https://img.shields.io/badge/PHP-8.4%2B-777BB4?logo=php&logoColor=white)](https://www.php.net/) +[![Composer](https://img.shields.io/badge/Composer-2.x-885630?logo=composer&logoColor=white)](https://getcomposer.org/) +[![License: MIT](https://img.shields.io/badge/License-MIT-22c55e.svg)](LICENSE) +[![PHPStan Level 9](https://img.shields.io/badge/PHPStan-Level%209-4F46E5)](https://phpstan.org/) +[![Tests](https://img.shields.io/badge/Tests-41%20passing-22c55e)](https://kariricode.org) +[![KaririCode Framework](https://img.shields.io/badge/KaririCode-Framework-orange)](https://kariricode.org) ---- - -## 📋 Table of Contents +**Unified quality toolchain for the KaririCode Framework ecosystem.** -- [Overview](#-overview) -- [Features](#-features) -- [Requirements](#-requirements) -- [Quick Start](#-quick-start) -- [Usage](#-usage) -- [Available Commands](#-available-commands) -- [Configuration](#-configuration) -- [Best Practices](#-best-practices) -- [Troubleshooting](#-troubleshooting) -- [Contributing](#-contributing) -- [License](#-license) - ---- +One dependency. One CLI. Zero config drift across 35+ components. -## 🎯 Overview +[Installation](#installation) · [Quick Start](#quick-start) · [CLI Reference](#cli-reference) · [Configuration](#configuration) · [CI Integration](#ci-integration) -**KaririCode DevKit** é um ambiente de desenvolvimento profissional e padronizado para todos os componentes do **KaririCode Framework**. Ele fornece: +
-- **Ambiente containerizado** usando Docker com imagem centralizada -- **Ferramentas de qualidade de código** pré-configuradas (PHPStan, PHP CS Fixer, PHPMD) -- **Makefile profissional** com mais de 50 comandos úteis -- **Configurações consistentes** para garantir qualidade e manutenibilidade -- **Integração CI/CD** pronta para GitHub Actions +--- -### Princípios de Design +## The Problem -- ✅ **SOLID** - Segregação de responsabilidades e inversão de dependência -- ✅ **Clean Code** - Código limpo, legível e manutenível -- ✅ **PSR Compliant** - Segue PSR-4, PSR-12 e PSR-6/16 -- ✅ **Strong Typing** - Tipagem forte em PHP 8.1+ -- ✅ **Test-Driven** - Estrutura completa para TDD -- ✅ **Performance-First** - Otimizado para produção +Every KaririCode component independently maintains five dev tools — leading to hundreds of near-identical, manually-drifting configurations: ---- +``` +composer.json → 5 require-dev entries per component +phpunit.xml.dist → ~60 lines per component +phpstan.neon → ~25 lines per component +.php-cs-fixer.dist.php → ~50 lines per component +rector.php → ~30 lines per component +psalm.xml → ~20 lines per component +``` -## ✨ Features +Across **35+ components**, that's **175+ redundant dependency entries** and **175+ near-identical config files**. Updating a single CS-Fixer rule means 35 pull requests. -### 🐳 Docker Environment +## The Solution -- Usa imagem centralizada: `kariricode/php-api-stack` -- Redis 7 Alpine para caching -- Memcached 1.6 Alpine para caching distribuído -- Xdebug 3 para debugging (ativação sob demanda) -- Configuração otimizada para desenvolvimento +```bash +composer require --dev kariricode/devkit +vendor/bin/kcode init +``` -### 🛠️ Code Quality Tools +Devkit generates `.kcode/` — a gitignored directory of configs derived directly from your `composer.json`. Your five dev dependencies become one, with one canonical source of truth: -| Tool | Version | Purpose | Config File | -|------|---------|---------|-------------| -| **PHPUnit** | 11.4 | Unit & Integration Testing | `phpunit.xml` | -| **PHPStan** | 2.0 | Static Analysis (Level Max) | `phpstan.neon` | -| **PHP CS Fixer** | 3.64 | Code Style (PSR-12+) | `.php-cs-fixer.php` | -| **PHPMD** | 2.15 | Mess Detection | `devkit/.config/phpmd/ruleset.xml` | -| **Rector** | 1.2 | Automated Refactoring | `rector.php` | -| **PHPBench** | 1.3 | Performance Benchmarking | N/A | +``` +your-component/ +├── devkit.php ← Optional overrides (committed to git) +├── .kcode/ ← Generated (gitignored — run `kcode init` to recreate) +│ ├── phpunit.xml.dist +│ ├── phpstan.neon +│ ├── php-cs-fixer.php +│ ├── rector.php +│ ├── psalm.xml +│ └── build/ ← Coverage reports, caches, JUnit XML +├── composer.json +├── src/ +└── tests/ +``` -### 📊 Coverage & Reports +```diff + "require-dev": { +- "phpunit/phpunit": "^12.0", +- "phpstan/phpstan": "^2.0", +- "friendsofphp/php-cs-fixer": "^3.64", +- "rector/rector": "^2.0", +- "vimeo/psalm": "^6.0" ++ "kariricode/devkit": "^1.0" + } +``` -- **HTML Coverage Report** - Visual coverage analysis -- **Clover XML** - For CI integration -- **JUnit XML** - For CI integration -- **TestDox** - Human-readable test documentation +--- -### 🎨 Editor Integration +## Requirements -- **EditorConfig** - Consistent formatting across editors -- **PHPStorm/VSCode** - Pre-configured settings -- **Git Hooks** - Pre-commit quality checks (optional) +| Requirement | Version | +|---|---| +| PHP | 8.4 or higher | +| Composer | 2.x | --- -## 📦 Requirements +## Installation -### Essential +### As a Composer dependency *(recommended)* -- **Docker** >= 20.10 -- **Docker Compose** >= 2.0 -- **Make** (usually pre-installed on Unix systems) +```bash +composer require --dev kariricode/devkit +``` -### Optional (for local development without Docker) +### As a standalone PHAR -- **PHP** >= 8.3 -- **Composer** >= 2.5 -- **Redis** (for cache testing) -- **Memcached** (for cache testing) +```bash +wget https://github.com/kariricode/devkit/releases/latest/download/kcode.phar +chmod +x kcode.phar +sudo mv kcode.phar /usr/local/bin/kcode +``` --- -## 🚀 Quick Start - -### Step 1: Clone for New Component +## Quick Start ```bash -# Clone the DevKit -git clone https://github.com/KaririCode-Framework/kariricode-devkit.git my-component +# Step 1 — Generate .kcode/ configs from your composer.json +vendor/bin/kcode init -cd my-component +# Step 2 — Remove old deps/configs (interactive prompt) +vendor/bin/kcode migrate -# Remove DevKit Git history -rm -rf .git - -# Initialize new repository -git init +# Step 3 — Run the full quality pipeline +vendor/bin/kcode quality ``` -### Step 2: Customize the Project +--- -```bash -# Edit composer.json with your component information -nano composer.json +## CLI Reference -# Create directory structure -mkdir -p src tests/{Unit,Integration} -``` +### `kcode init` -### Step 3: Initialize the Environment +Generates or regenerates all configs inside `.kcode/` from your `composer.json`. Safe to run at any time — existing files are cleanly overwritten. ```bash -# Starts containers, installs dependencies -make init +kcode init # Generate configs +kcode init --config # Also scaffold devkit.php with all available keys ``` -### Step 4: Start Developing - -```bash -# Access container shell -make shell - -# Run tests -make test - -# Check code quality -make check +``` +✓ Project: kariricode/parser +✓ Namespace: KaririCode\Parser +✓ PHP: 8.4 +✓ Generated 5 config file(s) in .kcode/ +✓ .kcode/ added to .gitignore +⚠ Found 8 redundant item(s) that kcode replaces. + Run kcode migrate to review and clean up. ``` --- -## 💻 Usage +### `kcode migrate` -### Typical Development Flow +Scans for redundant dev dependencies, root-level config files, and cache paths that Devkit now manages. Presents a full report before making any change. ```bash -# 1. Start environment -make up +kcode migrate # Interactive (default) +kcode migrate --dry-run # Preview only — no changes applied +kcode migrate --no-interaction # Auto-remove (for CI) +``` -# 2. Install dependencies -make install +**Detected items:** -# 3. Develop and test continuously -make test # Run all tests -make test-filter FILTER=MyTest # Test specific +| Category | Items | +|---|---| +| `require-dev` packages | `phpunit/phpunit`, `phpstan/phpstan`, `phpstan` extensions, `php-cs-fixer`, `rector/rector`, `vimeo/psalm` | +| Root config files | `phpunit.xml(.dist)`, `phpstan.neon(.dist)`, `.php-cs-fixer(.dist).php`, `rector.php`, `psalm.xml(.dist)` | +| Root cache paths | `.phpunit.cache`, `.phpunit.result.cache`, `.phpstan`, `.php-cs-fixer.cache`, `.psalm` | -# 4. Check quality before commit -make qa # Fix code style + Run tests + Static analysis +--- -# 5. Stop environment -make down -``` +### `kcode test` -### Git Integration +Runs PHPUnit with the `.kcode/phpunit.xml.dist` configuration. ```bash -# Recommended: run before each commit -make qa - -# Or configure pre-commit hook (optional) -cat << 'EOF' > .git/hooks/pre-commit -#!/bin/bash -make qa || exit 1 -EOF -chmod +x .git/hooks/pre-commit +kcode test # Run all test suites +kcode test --suite=Unit # Run a single suite +kcode test --coverage # Generate HTML coverage report +kcode test --filter=testMyMethod # Pass any PHPUnit argument through ``` --- -## 📖 Available Commands +### `kcode analyse` -### Docker Management +Runs PHPStan and Psalm in sequence. ```bash -make up # Start all containers -make down # Stop all containers -make restart # Restart containers -make status # Show container status -make logs # Show all logs -make logs-php # Show PHP container logs -make shell # Access PHP container shell -make shell-root # Access container as root +kcode analyse ``` -### Dependency Management +--- -```bash -make install # Install dependencies -make update # Update dependencies -make require PKG=vendor/package # Add package -make require-dev PKG=vendor/package # Add dev package -make autoload # Dump autoload -make validate # Validate composer.json -make outdated # Show outdated packages -``` +### `kcode cs:fix` -### Testing +Applies PHP-CS-Fixer with the KaririCode code standard. ```bash -make test # Run all tests -make test-coverage # Generate HTML coverage -make test-coverage-text # Show coverage in terminal -make test-unit # Run unit tests only -make test-integration # Run integration tests -make test-filter FILTER=TestName # Run specific test +kcode cs:fix # Fix all violations +kcode cs:fix --check # Dry-run — only report (no modifications) ``` -### Code Quality +--- -```bash -make cs-check # Check code style (dry-run) -make cs-fix # Fix code style -make analyse # Run PHPStan (level max) -make analyse-baseline # Generate PHPStan baseline -make phpmd # Run PHP Mess Detector -make rector # Run Rector (dry-run) -make rector-fix # Apply Rector changes -``` +### `kcode rector` -### Comprehensive Checks +Runs Rector. Defaults to read-only preview mode. ```bash -make check # Run CS check + PHPStan + PHPMD -make fix # Fix all auto-fixable issues -make qa # Complete pipeline: fix + test + check +kcode rector # Preview changes (no files modified) +kcode rector --fix # Apply changes ``` -### Security +--- -```bash -make security # Check for vulnerabilities (composer audit) -``` +### `kcode quality` -### Cache Operations +Full sequential pipeline in optimal order: -```bash -make redis-cli # Access Redis CLI -make redis-flush # Flush Redis cache -make redis-info # Show Redis information -make memcached-stats # Show Memcached statistics -make memcached-flush # Flush Memcached ``` - -### Utilities - -```bash -make clean # Clean generated files -make reset # Clean + remove containers -make rebuild # Complete rebuild from scratch -make permissions # Fix file permissions +cs:check → phpstan → psalm → phpunit ``` -### Xdebug - ```bash -make xdebug-on # Enable Xdebug -make xdebug-off # Disable Xdebug -make xdebug-status # Show Xdebug status +kcode quality ``` -### Information +``` +✓ cs-fixer passed (1.23s) +✓ phpstan passed (4.56s) +✓ psalm passed (3.21s) +✓ phpunit passed (2.10s) -```bash -make php-version # Show PHP version -make php-info # Show PHP configuration -make php-extensions # List PHP extensions -make composer-version # Show Composer version -make env # Show environment variables +✓ All 4 tool(s) passed (11.10s total) ``` -### CI/CD Simulation +--- -```bash -make ci # Simulate complete CI pipeline -``` +### `kcode format` -### Development Helpers +Applies all auto-formatting in sequence: CS-Fixer fix + Rector apply. ```bash -make watch-tests # Watch and run tests on changes -make init # Initialize new component (up + install) +kcode format ``` --- -## ⚙️ Configuration +### `kcode security` -### Environment Variables +Runs `composer audit` to check for known security vulnerabilities. -Create a `.env` file in the project root (optional): +```bash +kcode security +``` -```env -# Project name (affects container names) -COMPOSE_PROJECT_NAME=kariricode_cache +--- -# Xdebug -XDEBUG_MODE=off +### `kcode clean` -# Redis -REDIS_PORT=6379 +Removes `.kcode/build/` — caches, coverage reports, JUnit XML. -# Memcached -MEMCACHED_PORT=11211 +```bash +kcode clean ``` -### Tool Customization - -#### PHPUnit - -Edit `phpunit.xml` to adjust: -- Test suites -- Coverage settings -- Environment variables - -#### PHPStan +--- -Edit `phpstan.neon` to: -- Adjust analysis level (not recommended to lower from `max`) -- Add excluded paths -- Ignore specific errors (use sparingly) +## Configuration -#### PHP CS Fixer +### Auto-detection -Edit `.php-cs-fixer.php` to: -- Customize style rules -- Add exceptions +Devkit reads your `composer.json` to derive all defaults automatically: -#### PHPMD +| Detected Field | Source | +|---|---| +| Project name | `name` | +| Root namespace | `autoload.psr-4` (first key) | +| PHP version | `require.php` | +| Source directories | `autoload.psr-4` values | +| Test directories | `autoload-dev.psr-4` values | +| Test suites | Standard subdirs: `Unit/`, `Integration/`, `Conformance/`, `Functional/` | -Edit `devkit/.config/phpmd/ruleset.xml` to: -- Adjust complexity limits -- Disable specific rules +### Project overrides via `devkit.php` -### Recommended Directory Structure +Create `devkit.php` in your project root to override any default. Scaffold a fully-annotated file with: +```bash +kcode init --config ``` -your-component/ -├── devkit/ # DevKit files (do not commit to component) -│ └── .config/ -│ ├── php/ -│ │ ├── xdebug.ini -│ │ └── error-reporting.ini -│ └── phpmd/ -│ └── ruleset.xml -├── src/ # Component source code -│ ├── Adapter/ -│ ├── Contract/ -│ ├── Exception/ -│ └── ... -├── tests/ # Tests -│ ├── Unit/ -│ ├── Integration/ -│ └── bootstrap.php -├── .editorconfig -├── .gitignore -├── .php-cs-fixer.php -├── composer.json -├── docker-compose.yml -├── Makefile -├── phpstan.neon -├── phpunit.xml -├── README.md -└── LICENSE -``` - ---- -## 🎯 Best Practices - -### 1. Always Use Strong Typing +Example: ```php 8, // 0–9 (default: 9) + 'psalm_level' => 4, // 1–9 (default: 3) + 'exclude_dirs' => ['src/Contract'], // Excluded from analysis + 'test_suites' => [ + 'Unit' => 'tests/Unit', + 'Integration' => 'tests/Integration', + ], + 'coverage_exclude' => ['src/Exception'], + 'cs_fixer_rules' => [ // Merged with KaririCode defaults + 'yoda_style' => false, + ], + 'rector_sets' => [ // Replaces defaults entirely + 'LevelSetList::UP_TO_PHP_84', + 'SetList::CODE_QUALITY', + ], +]; +``` + +After editing, run `kcode init` to regenerate `.kcode/`. + +### Override reference + +| Key | Type | Default | Merge strategy | +|---|---|---|---| +| `project_name` | `string` | From `composer.json` | Replace | +| `namespace` | `string` | From PSR-4 autoload | Replace | +| `php_version` | `string` | From `require.php` | Replace | +| `phpstan_level` | `int` | `9` | Replace | +| `psalm_level` | `int` | `3` | Replace | +| `source_dirs` | `list` | From PSR-4 autoload | Replace | +| `test_dirs` | `list` | From PSR-4 autoload-dev | Replace | +| `exclude_dirs` | `list` | `['src/Contract']` | Replace | +| `test_suites` | `array` | Auto-detected | Replace | +| `coverage_exclude` | `list` | `['src/Exception']` | Replace | +| `cs_fixer_rules` | `array` | KaririCode standard | **Merge** (your rules win) | +| `rector_sets` | `list` | KaririCode standard | Replace | +| `tools` | `array` | — | Informational | + +### File ownership + +| File | Location | Committed | Managed by | +|---|---|---|---| +| `devkit.php` | Project root | ✅ Yes | Developer | +| `.kcode/` | Generated dir | ❌ No (gitignored) | `kcode init` | + +### KaririCode coding standard + +The default CS-Fixer ruleset includes: + +- **PSR-12** baseline with **PHP 8.4 migration** rules +- Strict types enforcement (`declare_strict_types`) +- Compiler-optimized native function invocations +- Alphabetically ordered imports +- Trailing commas in multiline arrays, arguments, and parameters + +See [SPEC-001](docs/spec/SPEC-001-project-detection.md) §7 for the complete rule set. -final class Example -{ - public function __construct( - private readonly string $name, - private readonly int $age - ) { - } +--- - public function getName(): string - { - return $this->name; - } -} +## CI Integration + +### GitHub Actions — unified pipeline + +```yaml +name: Quality +on: [push, pull_request] +jobs: + quality: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + coverage: pcov + - run: composer install --no-progress --no-scripts + - run: vendor/bin/kcode init + - run: vendor/bin/kcode migrate --no-interaction + - run: vendor/bin/kcode quality +``` + +### GitHub Actions — parallel jobs + +```yaml +jobs: + cs-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: shivammathur/setup-php@v2 + with: { php-version: '8.4' } + - run: composer install --no-progress --no-scripts + - run: vendor/bin/kcode init && vendor/bin/kcode cs:fix --check + + analyse: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: shivammathur/setup-php@v2 + with: { php-version: '8.4' } + - run: composer install --no-progress --no-scripts + - run: vendor/bin/kcode init && vendor/bin/kcode analyse + + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: shivammathur/setup-php@v2 + with: { php-version: '8.4', coverage: pcov } + - run: composer install --no-progress --no-scripts + - run: vendor/bin/kcode init && vendor/bin/kcode test --coverage ``` -### 2. Follow SOLID - -```php -// ✅ Good: Dependency Injection -public function __construct( - private readonly CacheInterface $cache -) { -} - -// ❌ Bad: Direct instantiation -public function __construct() -{ - $this->cache = new RedisCache(); -} -``` +--- -### 3. Write Tests First (TDD) +## Migrating from Root-Level Configs -```php -// tests/Unit/ExampleTest.php -final class ExampleTest extends TestCase -{ - public function testItWorks(): void - { - $example = new Example('John', 30); - - $this->assertSame('John', $example->getName()); - } -} -``` +```bash +# 1. Add devkit as a dev dependency +composer require --dev kariricode/devkit -### 4. Documente Código Complexo +# 2. Generate .kcode/ configs +vendor/bin/kcode init -```php -/** - * Processa dados usando estratégia de cache multinível. - * - * Este método implementa cache em camadas com fallback automático. - * Primeiro tenta L1 (memória), depois L2 (Redis), e finalmente - * busca da fonte original. - * - * Performance characteristics: - * - Time complexity: O(1) para cache hit - * - Space complexity: O(n) onde n é o tamanho dos dados - * - * @param array $data Dados a processar - * @param positive-int $ttl Tempo de vida em segundos - * - * @return array Dados processados e cacheados - * - * @throws CacheException Se cache falhar - * @throws InvalidArgumentException Se dados inválidos - * - * @example - * ```php - * $result = $cache->process(['key' => 'value'], 3600); - * ``` - * - * @see https://kariricode.org/docs/cache/multilayer - * @since 1.0.0 - */ -public function process(array $data, int $ttl): array -{ - // Implementation -} -``` - -**Padrão de Documentação:** -- ✅ Descrição clara e concisa -- ✅ Explicação detalhada quando necessário -- ✅ Performance characteristics (opcional) -- ✅ Todos os @param com tipos precisos -- ✅ @return com tipo de retorno -- ✅ @throws para todas as exceções -- ✅ @example com código funcional -- ✅ @see para referências externas -- ✅ @since para versionamento - -> **PHP CS Fixer está configurado para MANTER toda a documentação!** -> Diferente de configurações padrão, nossa setup preserva @author, @package, -> @category, e todos os outros tags importantes. - -### 5. Use Make Commands +# 3. Review and remove redundant files (interactive) +vendor/bin/kcode migrate -```bash -# Antes de cada commit -make qa +# 4. Apply composer.json changes +composer update -# Durante desenvolvimento -make test-filter FILTER=MyNewFeature +# 5. Verify the pipeline +vendor/bin/kcode quality ``` ---- - -## 🔧 Troubleshooting +Use `--dry-run` to inspect what would be removed before committing. -### Common Issues - -#### 1. **Containers won't start** +--- -```bash -# Check if ports are in use -docker ps -a +## Architecture -# Remove old containers -make clean -docker system prune -a +### Component layout -# Rebuild -make rebuild ``` - -#### 2. **File permissions** - -```bash -# Fix permissions -make permissions - -# Or manually -docker-compose exec -u root php chown -R app:app /var/www +src/ +├── Contract/ Interfaces: ConfigGenerator, ToolRunner +├── Core/ Devkit façade · ProjectDetector · ProcessExecutor +├── Configuration/ Config generators (5 tools) +├── Runner/ Tool runners (6 runners + AbstractToolRunner) +├── Command/ CLI commands (10 commands + Application + AbstractCommand) +├── Exception/ Exception hierarchy +└── ValueObject/ Immutable results: ToolResult · QualityReport · MigrationReport ``` -#### 3. **Dependencies won't install** +### Dependency flow -```bash -# Clear Composer cache -docker-compose exec php composer clear-cache - -# Reinstall -make clean -make install ``` - -#### 4. **Xdebug not working** - -```bash -# Check if enabled -make xdebug-status - -# Enable -make xdebug-on - -# Configure IDE for port 9003 -# host: localhost -# port: 9003 +Command → Core (Devkit) → Contract ← Runner / Configuration ``` -#### 5. **Tests fail on CI but pass locally** +Strict unidirectional flow. No circular dependencies. Commands call the `Devkit` façade. Runners and generators implement contracts. Core orchestrates. -```bash -# Simulate CI environment -make ci +### Key design decisions -# Check environment differences -docker-compose exec php php -v -docker-compose exec php php -m -``` +| Decision | Rationale | ADR | +|---|---|---| +| Native PHAR builder | Avoids Box 4.x / PHP 8.4 incompatibility | — | +| Zero external runtime dependencies | Sub-millisecond boot, no version conflicts | [ADR-002](docs/adr/ADR-002-zero-dependencies.md) | +| Config generation over bundling | Eliminates drift across 35+ components | [ADR-003](docs/adr/ADR-003-config-generation.md) | +| Three-tier binary resolution | PHAR → vendor → global fallback | [ADR-004](docs/adr/ADR-004-binary-resolution.md) | +| `.kcode/` directory | Clean project root, single gitignore entry | [ADR-005](docs/adr/ADR-005-kariricode-directory.md) | +| Immutable value objects | Thread-safe, ARFA 1.3 compliant | [ADR-006](docs/adr/ADR-006-immutable-value-objects.md) | -### Logs and Debug +### Specifications -```bash -# View all logs -make logs - -# Specific logs -make logs-php - -# Access shell for debugging -make shell -``` +| Spec | Covers | +|---|---| +| [SPEC-001](docs/spec/SPEC-001-project-detection.md) | Project detection, config merging, defaults | +| [SPEC-002](docs/spec/SPEC-002-cli-interface.md) | CLI interface, argument parsing, output format | +| [SPEC-003](docs/spec/SPEC-003-tool-runner.md) | Runner contract, process execution, result handling | --- -## 🤝 Contributing +## Project Stats -Contributions are welcome! To contribute: +| Metric | Value | +|---|---| +| PHP source files | 38 | +| Total source lines | ~2,900 | +| External runtime dependencies | 0 | +| Quality tools supported | 6 (PHPUnit, PHPStan, PHP-CS-Fixer, Rector, Psalm, Composer Audit) | +| CLI commands | 10 | +| PHPStan level | 9 (0 errors) | +| Test suite | 41 tests · 81 assertions | +| PHP version | 8.4+ | +| ARFA compliance | 1.3 | -1. **Fork** the repository -2. **Create** a branch for your feature (`git checkout -b feature/amazing-feature`) -3. **Commit** your changes (`git commit -m 'Add amazing feature'`) -4. **Push** to the branch (`git push origin feature/amazing-feature`) -5. **Open** a Pull Request +--- -### Guidelines +## Building `kcode.phar` -- Follow SOLID principles and Clean Code -- Maintain test coverage >= 80% -- Run `make qa` before committing -- Document changes in README if necessary -- Use [Conventional Commits](https://www.conventionalcommits.org/) +```bash +# Requirements: PHP 8.4+ · Composer 2.x · phar.readonly=0 ---- +# Full release pipeline (recommended) +make release -## 📄 License +# Manual +composer install +php -d phar.readonly=0 bin/build-phar.php +php build/kcode.phar --version # → KaririCode Devkit 1.0.0 -This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. +# Install globally +sudo mv build/kcode.phar /usr/local/bin/kcode +``` + +See [docs/BUILDING.md](docs/BUILDING.md) for full build documentation, troubleshooting, and release automation details. --- -## 🔗 Useful Links +## Contributing + +```bash +git clone https://github.com/kariricode/devkit.git +cd devkit +composer install +vendor/bin/kcode init +vendor/bin/kcode quality # Must pass before opening a PR +``` -- **KaririCode Framework**: https://kariricode.org -- **Documentation**: https://kariricode.org/docs -- **GitHub Organization**: https://github.com/KaririCode-Framework -- **Packagist**: https://packagist.org/packages/kariricode/ +CI enforces code quality and a PHAR smoke test on every push and PR. --- -## 👥 Maintainers +## License -- **Walmir Silva** - [walmir.silva@kariricode.org](mailto:walmir.silva@kariricode.org) +[MIT License](LICENSE) © [Walmir Silva](mailto:walmir.silva@kariricode.org) --- -## 🙏 Acknowledgments - -- PHP-FIG for PSR standards -- Symfony Cache Component for inspiration -- Docker community for containerization best practices -- All contributors to the KaririCode Framework +
---- +Part of the **[KaririCode Framework](https://kariricode.org)** ecosystem. -**Built with ❤️ by the KaririCode Team** +[kariricode.org](https://kariricode.org) · [GitHub](https://github.com/kariricode) · [Packagist](https://packagist.org/packages/kariricode/) -*Empowering developers to build robust, maintainable, and professional PHP applications* \ No newline at end of file +
diff --git a/bin/build-phar.php b/bin/build-phar.php new file mode 100644 index 0000000..d137753 --- /dev/null +++ b/bin/build-phar.php @@ -0,0 +1,105 @@ +startBuffering(); + +echo "📦 Building kcode.phar...\n"; + +// ── 1. Add src/ ──────────────────────────────────────────── +$added = 0; +$srcDir = $root . '/src'; +$it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($srcDir, FilesystemIterator::SKIP_DOTS)); +foreach ($it as $file) { + if ($file->isFile() && $file->getExtension() === 'php') { + $relative = 'src/' . $it->getSubPathname(); + $phar[$relative] = file_get_contents($file->getPathname()); + $added++; + } +} +echo " + src/: $added PHP files\n"; + +// ── 2. Add vendor/ (PHP files only, no tests/docs) ───────── +$vendorDir = $root . '/vendor'; +$excludeDirs = ['Tests', 'tests', 'test', 'doc', 'docs', 'examples', '.github']; + +$vendorIt = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($vendorDir, FilesystemIterator::SKIP_DOTS), + RecursiveIteratorIterator::LEAVES_ONLY +); + +$vendorAdded = 0; +foreach ($vendorIt as $file) { + if (!$file->isFile()) continue; + $path = $file->getPathname(); + + // Skip test/doc directories + $skip = false; + foreach ($excludeDirs as $ex) { + if (str_contains($path, DIRECTORY_SEPARATOR . $ex . DIRECTORY_SEPARATOR)) { + $skip = true; + break; + } + } + if ($skip) continue; + + // Only PHP and JSON files + $ext = $file->getExtension(); + if (!in_array($ext, ['php', 'json'], true)) continue; + + $relative = 'vendor/' . substr($path, strlen($vendorDir) + 1); + $phar[$relative] = file_get_contents($path); + $vendorAdded++; +} +echo " + vendor/: $vendorAdded files\n"; + +// ── 3. Add LICENSE ────────────────────────────────────────── +$phar['LICENSE'] = file_get_contents($root . '/LICENSE'); +echo " + LICENSE\n"; + +// ── 4. Set bin/kcode as the entry point (stub) ───────────── +$kcodeContent = file_get_contents($root . '/bin/kcode'); +$phar['bin/kcode'] = $kcodeContent; + +$stub = <<<'STUB' +#!/usr/bin/env php + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ +Phar::mapPhar('kcode.phar'); +require 'phar://kcode.phar/bin/kcode'; +__HALT_COMPILER(); +STUB; + +$phar->setStub($stub); +echo " + stub (bin/kcode entry point)\n"; + +// ── 5. Finalize ───────────────────────────────────────────── +$phar->stopBuffering(); +$phar->compressFiles(Phar::GZ); +chmod($output, 0755); + +$size = round(filesize($output) / 1024 / 1024, 2); +echo "\n✅ Built: $output ($size MB)\n"; +echo " Files: " . count($phar) . "\n"; diff --git a/bin/kcode b/bin/kcode new file mode 100755 index 0000000..0fd2af8 --- /dev/null +++ b/bin/kcode @@ -0,0 +1,94 @@ +#!/usr/bin/env php +addGenerator(new \KaririCode\Devkit\Configuration\PhpUnitConfigGenerator()); + $devkit->addGenerator(new \KaririCode\Devkit\Configuration\PhpStanConfigGenerator()); + $devkit->addGenerator(new \KaririCode\Devkit\Configuration\CsFixerConfigGenerator()); + $devkit->addGenerator(new \KaririCode\Devkit\Configuration\RectorConfigGenerator()); + $devkit->addGenerator(new \KaririCode\Devkit\Configuration\PsalmConfigGenerator()); + + // Register tool runners — require ProcessExecutor and ProjectContext + $executor = new \KaririCode\Devkit\Core\ProcessExecutor($workingDirectory); + + // Detect project context eagerly — runners need it at registration. + // Graceful null when no composer.json found (help/version still work). + try { + $context = $devkit->context($workingDirectory); + } catch (\KaririCode\Devkit\Exception\DevkitException) { + // Help and version work without project context. + // Commands requiring context will re-detect and throw at execution time. + $context = null; + } + + if (null !== $context) { + $devkit->addRunner(new \KaririCode\Devkit\Runner\PhpUnitRunner($executor, $context)); + $devkit->addRunner(new \KaririCode\Devkit\Runner\PhpStanRunner($executor, $context)); + $devkit->addRunner(new \KaririCode\Devkit\Runner\CsFixerRunner($executor, $context)); + $devkit->addRunner(new \KaririCode\Devkit\Runner\RectorRunner($executor, $context)); + $devkit->addRunner(new \KaririCode\Devkit\Runner\PsalmRunner($executor, $context)); + $devkit->addRunner(new \KaririCode\Devkit\Runner\ComposerAuditRunner($executor, $context)); + } + + // ── Wire CLI commands ───────────────────────────────────────── + + $app = new \KaririCode\Devkit\Command\Application($devkit); + + $app->register(new \KaririCode\Devkit\Command\InitCommand()); + $app->register(new \KaririCode\Devkit\Command\MigrateCommand( + new \KaririCode\Devkit\Core\MigrationDetector(), + )); + $app->register(new \KaririCode\Devkit\Command\TestCommand()); + $app->register(new \KaririCode\Devkit\Command\AnalyseCommand()); + $app->register(new \KaririCode\Devkit\Command\CsFixCommand()); + $app->register(new \KaririCode\Devkit\Command\RectorCommand()); + $app->register(new \KaririCode\Devkit\Command\SecurityCommand()); + $app->register(new \KaririCode\Devkit\Command\QualityCommand()); + $app->register(new \KaririCode\Devkit\Command\FormatCommand()); + $app->register(new \KaririCode\Devkit\Command\CleanCommand()); + + // ── Dispatch ────────────────────────────────────────────────── + + exit($app->run($argv)); +})($argv ?? []); diff --git a/box.json b/box.json new file mode 100644 index 0000000..fb393ab --- /dev/null +++ b/box.json @@ -0,0 +1,64 @@ +{ + "main": "bin/kcode", + "output": "build/kcode.phar", + + "directories": [ + "src" + ], + + "finder": [ + { + "name": "*.php", + "in": ["vendor"], + "exclude": [ + "Tests", + "tests", + "test", + "doc", + "docs", + "examples" + ] + } + ], + + "files": [ + "vendor/autoload.php", + "LICENSE" + ], + + "blacklist": [ + "box.json", + "composer.lock", + "Makefile", + "README.md", + "CHANGELOG.md", + "phpunit.xml.dist", + "phpstan.neon", + ".php-cs-fixer.dist.php" + ], + + "compactors": [ + "KevinGH\\Box\\Compactor\\Php" + ], + + "compression": "GZ", + "chmod": "0755", + "stub": true, + "alias": "kcode.phar", + + "banner": [ + "KaririCode Devkit — kcode", + "", + "Unified quality toolchain for KaririCode Framework.", + "", + "(c) Walmir Silva ", + "", + "For the full copyright and license information, please view", + "the LICENSE file that was distributed with this source code." + ], + + "metadata": { + "tool": "KaririCode Devkit", + "version": "1.0.0" + } +} diff --git a/composer.json b/composer.json index 5379c3d..c4686c3 100644 --- a/composer.json +++ b/composer.json @@ -1,63 +1,74 @@ { "name": "kariricode/devkit", - "description": "Professional development environment for KaririCode Framework components", - "type": "project", + "description": "Unified quality toolchain for KaririCode Framework — encapsulates PHPUnit, PHPStan, PHP-CS-Fixer, Rector, and Psalm in .kcode/", + "type": "library", + "license": "MIT", "keywords": [ "kariricode", "devkit", - "development-environment", - "docker", - "php" + "quality", + "toolchain", + "phpunit", + "phpstan", + "php-cs-fixer", + "rector", + "psalm", + "static-analysis", + "code-style" ], - "license": "MIT", + "homepage": "https://kariricode.org", "authors": [ { "name": "Walmir Silva", - "email": "walmir.silva@kariricode.org", - "homepage": "https://kariricode.org", - "role": "Developer" + "email": "walmir.silva@kariricode.org" } ], - "homepage": "https://github.com/KaririCode-Framework/kariricode-devkit", - "support": { - "issues": "https://github.com/KaririCode-Framework/kariricode-devkit/issues", - "source": "https://github.com/KaririCode-Framework/kariricode-devkit", - "docs": "https://kariricode.org/docs/devkit" + "require": { + "php": ">=8.4" + }, + "require-dev": { + "phpunit/phpunit": "^12.0", + "phpstan/phpstan": "^2.0", + "friendsofphp/php-cs-fixer": "^3.64", + "rector/rector": "^2.0", + "vimeo/psalm": "^6.0" }, "autoload": { "psr-4": { - "KaririCode\\DevKit\\": "src/" + "KaririCode\\Devkit\\": "src/" } }, "autoload-dev": { "psr-4": { - "KaririCode\\DevKit\\Tests\\": "tests/" + "KaririCode\\Devkit\\Tests\\": "tests/" } }, - "require": { - "php": "^8.4" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.89", - "infection/infection": "^0.31", - "phpbench/phpbench": "^1.3", - "phpstan/phpstan": "^2.1", - "phpstan/phpstan-strict-rules": "^2.0", - "phpunit/phpunit": "^12.4", - "squizlabs/php_codesniffer": "^4.0", - "symfony/var-dumper": "^7.3", - "vimeo/psalm": "^6.13" + "bin": [ + "bin/kcode" + ], + "scripts": { + "kcode:init": "bin/kcode init", + "kcode:test": "bin/kcode test", + "kcode:analyse": "bin/kcode analyse", + "kcode:cs": "bin/kcode cs:fix", + "kcode:cs-check": "bin/kcode cs:fix --check", + "kcode:quality": "bin/kcode quality", + "kcode:migrate": "bin/kcode migrate", + "build": "php -d phar.readonly=0 bin/build-phar.php" }, "config": { "sort-packages": true, - "optimize-autoloader": true, "preferred-install": "dist", + "optimize-autoloader": true, "allow-plugins": { - "php-http/discovery": true, - "dealerdirect/phpcodesniffer-composer-installer": true, - "infection/extension-installer": true + "infection/extension-installer": false + } + }, + "extra": { + "branch-alias": { + "dev-main": "1.0.x-dev" } }, - "minimum-stability": "dev", + "minimum-stability": "stable", "prefer-stable": true } diff --git a/devkit/.config/php/error-reporting.ini b/devkit/.config/php/error-reporting.ini deleted file mode 100644 index 8fec4ec..0000000 --- a/devkit/.config/php/error-reporting.ini +++ /dev/null @@ -1,145 +0,0 @@ -; ============================================================================ -; KaririCode DevKit - Error Reporting Configuration -; ============================================================================ -; Strict error reporting configuration for development environment -; All errors, warnings, and notices are displayed to catch issues early -; -; Location: devkit/.config/php/error-reporting.ini -; ============================================================================ - -[PHP] -; ============================================================================ -; ERROR REPORTING -; ============================================================================ -; Report all errors, warnings, and notices -error_reporting = E_ALL - -; Display errors on screen (development only) -display_errors = On -display_startup_errors = On - -; Log errors to file -log_errors = On -error_log = /var/log/php_errors.log - -; Detailed error messages -html_errors = On -docref_root = "https://www.php.net/manual/en/" -docref_ext = .html - -; Track all errors -track_errors = Off -xmlrpc_errors = Off - -; ============================================================================ -; ASSERTIONS -; ============================================================================ -; Enable assertions for development -zend.assertions = 1 -assert.active = 1 -assert.exception = 1 -assert.bail = 0 - -; ============================================================================ -; DEVELOPMENT SETTINGS -; ============================================================================ -; Hide PHP version in headers (security) -expose_php = Off - -; Variables order -variables_order = "EGPCS" -request_order = "GP" - -; Auto-detect line endings -auto_detect_line_endings = Off - -; ============================================================================ -; RESOURCE LIMITS -; ============================================================================ -; Memory limit (generous for development) -memory_limit = 512M - -; Maximum execution time -max_execution_time = 30 -max_input_time = 60 - -; Input size limits -post_max_size = 25M -upload_max_filesize = 20M -max_file_uploads = 20 - -; ============================================================================ -; OUTPUT BUFFERING -; ============================================================================ -; Output buffering (off for immediate error display) -output_buffering = Off -implicit_flush = On - -; ============================================================================ -; DATE/TIME -; ============================================================================ -; Default timezone -date.timezone = UTC - -; ============================================================================ -; SESSION -; ============================================================================ -; Session configuration -session.save_handler = files -session.save_path = "/tmp" -session.use_strict_mode = 1 -session.use_cookies = 1 -session.use_only_cookies = 1 -session.cookie_httponly = 1 -session.cookie_secure = 0 -session.cookie_samesite = "Lax" -session.gc_probability = 1 -session.gc_divisor = 100 -session.gc_maxlifetime = 1440 - -; ============================================================================ -; REALPATH CACHE -; ============================================================================ -; Realpath cache (keep small for development) -realpath_cache_size = 4096K -realpath_cache_ttl = 120 - -; ============================================================================ -; FILE UPLOADS -; ============================================================================ -; File uploads enabled -file_uploads = On -upload_tmp_dir = /tmp - -; ============================================================================ -; SECURITY -; ============================================================================ -; Disable dangerous functions (customize as needed) -disable_functions = -disable_classes = - -; ============================================================================ -; MAIL -; ============================================================================ -; Mail configuration (usually handled by application) -SMTP = localhost -smtp_port = 25 -sendmail_path = /usr/sbin/sendmail -t -i - -; ============================================================================ -; MISC -; ============================================================================ -; Allow URL fopen (needed for many libraries) -allow_url_fopen = On -allow_url_include = Off - -; Auto-prepend/append files -auto_prepend_file = -auto_append_file = - -; Default charset -default_charset = "UTF-8" - -; Maximum input variables (prevent resource exhaustion) -max_input_vars = 1000 -max_input_nesting_level = 64 \ No newline at end of file diff --git a/devkit/.config/php/opcache.ini b/devkit/.config/php/opcache.ini deleted file mode 100644 index 32af5cc..0000000 --- a/devkit/.config/php/opcache.ini +++ /dev/null @@ -1,15 +0,0 @@ -; --- OPcache base (FPM + CLI) --- -zend_extension=opcache.so - -opcache.enable=1 -opcache.enable_cli=1 - -; Tuning sugerido p/ dev -opcache.memory_consumption=192 -opcache.interned_strings_buffer=16 -opcache.max_accelerated_files=20000 -opcache.validate_timestamps=1 -opcache.revalidate_freq=2 -opcache.fast_shutdown=1 -; Evita cache de arquivos que mudam muito em dev, se preferir -; opcache.file_update_protection=0 diff --git a/devkit/.config/php/xdebug.ini b/devkit/.config/php/xdebug.ini deleted file mode 100644 index 462643e..0000000 --- a/devkit/.config/php/xdebug.ini +++ /dev/null @@ -1,160 +0,0 @@ -; ============================================================================ -; KaririCode DevKit - Xdebug Configuration -; ============================================================================ -; Xdebug 3.x configuration for step debugging and code coverage -; https://xdebug.org/docs/all_settings -; -; Location: devkit/.config/php/xdebug.ini -; ============================================================================ - -[xdebug] -; ============================================================================ -; MODE CONFIGURATION -; ============================================================================ -; Modes: off, develop, coverage, debug, gcstats, profile, trace -; Multiple modes can be combined with commas (e.g., "debug,coverage") -; This is controlled by environment variable XDEBUG_MODE in .env -xdebug.mode=${XDEBUG_MODE} - -; ============================================================================ -; DEBUGGING -; ============================================================================ -; Start debugging automatically or wait for trigger -; Values: yes, no, trigger -xdebug.start_with_request=yes - -; IDE/Client connection settings -; Use host.docker.internal for Docker Desktop (Mac/Windows) -; Use 172.17.0.1 for Docker on Linux -xdebug.client_host=host.docker.internal -xdebug.client_port=9003 - -; Discovery mode for cloud/dynamic environments -; Set to 1 if you need automatic discovery (not recommended for local dev) -xdebug.discover_client_host=0 - -; IDE key for identifying debugging session -; PHPStorm: PHPSTORM -; VSCode: VSCODE -xdebug.idekey=PHPSTORM - -; Connection timeout in milliseconds -xdebug.connect_timeout_ms=2000 - -; ============================================================================ -; LOGGING -; ============================================================================ -; Log file location (useful for debugging connection issues) -xdebug.log=/var/log/xdebug.log - -; Log level (0-10, where 10 is most verbose) -; 0 = Criticals -; 1 = Errors -; 3 = Warnings -; 5 = Communication -; 7 = Information -; 10 = Debug -xdebug.log_level=7 - -; ============================================================================ -; STEP DEBUGGING -; ============================================================================ -; Maximum nesting level for recursive debugging -; Increase if you have deeply nested structures -xdebug.max_nesting_level=512 - -; ============================================================================ -; COVERAGE -; ============================================================================ -; Enable code coverage (required for PHPUnit coverage) -xdebug.coverage_enable=1 - -; ============================================================================ -; DEVELOPMENT MODE -; ============================================================================ -; Development helpers (when mode=develop) -; Show local variables in stack traces -xdebug.dump.GET=* -xdebug.dump.POST=* -xdebug.dump.COOKIE=* -xdebug.dump.FILES=* -xdebug.dump.SESSION=* - -; ============================================================================ -; PROFILING (disabled by default) -; ============================================================================ -; Uncomment to enable profiling -; xdebug.profiler_enable=0 -; xdebug.profiler_enable_trigger=1 -; xdebug.profiler_enable_trigger_value="" -; xdebug.profiler_output_dir=/var/www/profiler -; xdebug.profiler_output_name=cachegrind.out.%p - -; ============================================================================ -; TRACING (disabled by default) -; ============================================================================ -; Uncomment to enable function tracing -; xdebug.trace_enable_trigger=1 -; xdebug.trace_enable_trigger_value="" -; xdebug.trace_output_dir=/var/www/traces -; xdebug.trace_output_name=trace.%c -; xdebug.trace_format=0 -; xdebug.trace_options=0 - -; ============================================================================ -; PERFORMANCE -; ============================================================================ -; Show memory usage in stack traces -xdebug.show_mem_delta=1 - -; ============================================================================ -; DISPLAY -; ============================================================================ -; HTML error output formatting -xdebug.cli_color=1 - -; Variable display depth -xdebug.var_display_max_depth=10 - -; Maximum number of array children/object properties -xdebug.var_display_max_children=256 - -; Maximum string length -xdebug.var_display_max_data=4096 - -; ============================================================================ -; USAGE TIPS -; ============================================================================ -; -; Enable Xdebug: -; make xdebug-on -; -; Disable Xdebug: -; make xdebug-off -; -; Check Status: -; make xdebug-status -; -; IDE Configuration: -; PHPStorm: -; - Settings > PHP > Debug -; - Port: 9003 -; - Check "Accept external connections" -; - Settings > PHP > Servers -; - Add server: localhost, port 9003 -; - Map: /var/www -> your-project-path -; -; VSCode: -; - Install PHP Debug extension -; - Add to launch.json: -; { -; "name": "Listen for Xdebug", -; "type": "php", -; "request": "launch", -; "port": 9003, -; "pathMappings": { -; "/var/www": "${workspaceFolder}" -; } -; } -; -; ============================================================================ \ No newline at end of file diff --git a/devkit/.config/phpmd/ruleset.xml b/devkit/.config/phpmd/ruleset.xml deleted file mode 100644 index 0c05d4d..0000000 --- a/devkit/.config/phpmd/ruleset.xml +++ /dev/null @@ -1,273 +0,0 @@ - - - - - - Professional PHPMD ruleset for KaririCode Framework components. - Enforces clean code principles, SOLID design, and maintainability. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */tests/* - */Tests/* - - - */vendor/* - - - */cache/* - */storage/* - */temp/* - */tmp/* - - - */build/* - */coverage/* - */docs/* - - - */migrations/* - */database/factories/* - */database/seeders/* - - - */_ide_helper*.php - *.blade.php - - - */config/* - - - */public/* - - - - \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 3c788e0..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,423 +0,0 @@ -# ============================================================================== -# KaririCode DevKit - Professional Docker Compose Configuration -# ============================================================================== -# Production-grade development environment following Docker best practices -# -# Features: -# - Resource limits and reservations -# - Health checks with proper timeouts -# - Security hardening (read-only mounts, tmpfs, capabilities) -# - Structured logging -# - Multi-environment profiles -# - Named volumes for performance -# - Proper dependency management -# -# Usage: -# Development: make up-safe -# Production: docker compose --profile production up -# Testing: docker compose --profile testing up -# -# Documentation: https://docs.docker.com/compose/compose-file/ -# ============================================================================== - -# Modern Compose syntax (no version needed for Compose v2+) -# Ref: https://docs.docker.com/compose/compose-file/04-version-and-name/ -name: ${APP_NAME:-kariricode-devkit} - -services: - # ============================================================================ - # PHP-FPM + Nginx + Redis Service (kariricode/php-api-stack) - # ============================================================================ - php: - image: kariricode/php-api-stack:${PHP_STACK_VERSION:-dev} - container_name: ${APP_NAME:-kariricode-devkit}_php - hostname: php-app - - # Restart policy for production stability - restart: unless-stopped - - # Working directory - working_dir: /var/www/html - - # User and group for running the application (security best practice) - # NOTE: Commented out - image runs as root internally but processes run as www-data - # user: "${UID:-1000}:${GID:-1000}" - - # Resource limits (prevent runaway processes) - deploy: - resources: - limits: - cpus: "${PHP_CPU_LIMIT:-2.0}" - memory: ${PHP_MEMORY_LIMIT:-2G} - reservations: - cpus: "${PHP_CPU_RESERVATION:-0.5}" - memory: ${PHP_MEMORY_RESERVATION:-512M} - - # Port mappings - ports: - - target: 80 - published: ${APP_PORT:-8089} - protocol: tcp - mode: host - - target: 6379 - published: ${REDIS_PORT:-6379} - protocol: tcp - mode: host - - env_file: - - .env - # Volume mounts - volumes: - # Application code (delegated for macOS performance) - - type: bind - source: ./ - target: /var/www/html - consistency: ${VOLUME_CONSISTENCY:-cached} - - # Named volumes for cache (performance optimization) - - composer-cache:/root/.composer/cache - - phpstan-cache:/var/www/html/var/cache/phpstan - - php-session:/var/lib/php/sessions - - # Temporary filesystem for uploads (security + performance) - - type: tmpfs - target: /tmp - tmpfs: - size: ${TMPFS_SIZE:-100M} - mode: 1777 - - # # Environment variables - # environment: - # # Application - # APP_ENV: ${APP_ENV:-development} - # APP_DEBUG: ${APP_DEBUG:-true} - # APP_SECRET: ${APP_SECRET:-change-me-in-production} - # APP_VERSION: ${APP_VERSION:-dev} - # SYMFONY_ENV: ${SYMFONY_ENV:-dev} - - # # Session Handler (CRITICAL) - # SESSION_SAVE_HANDLER: ${SESSION_SAVE_HANDLER:-files} - # SESSION_SAVE_PATH: ${SESSION_SAVE_PATH:-/tmp} - - # # Redis Configuration (Internal - inside container) - # REDIS_HOST: ${REDIS_HOST:-127.0.0.1} - # REDIS_PORT: ${REDIS_PORT_INTERNAL:-6379} - # REDIS_PASSWORD: ${REDIS_PASSWORD:-} - # REDIS_DB: ${REDIS_DB:-0} - # REDIS_TIMEOUT: ${REDIS_TIMEOUT:-5} - - # # Redis Server Configuration - # REDIS_LOGFILE: ${REDIS_LOGFILE:-/var/log/redis/redis.log} - # REDIS_LOGLEVEL: ${REDIS_LOGLEVEL:-notice} - # REDIS_MAXMEMORY: ${REDIS_MAXMEMORY:-256mb} - # REDIS_MAXMEMORY_POLICY: ${REDIS_MAXMEMORY_POLICY:-allkeys-lru} - # REDIS_SAVE: ${REDIS_SAVE:-} - # REDIS_APPENDONLY: ${REDIS_APPENDONLY:-no} - # REDIS_LOG_FILE: ${REDIS_LOG_FILE:-/var/log/redis/redis.log} - - # # Memcached Configuration (External service) - # MEMCACHED_HOST: memcached - # MEMCACHED_PORT: 11211 - - # # PHP Configuration - # PHP_MEMORY_LIMIT: ${PHP_MEMORY_LIMIT:-2G} - # PHP_MAX_EXECUTION_TIME: ${PHP_MAX_EXECUTION_TIME:-300} - # PHP_UPLOAD_MAX_FILESIZE: ${PHP_UPLOAD_MAX_FILESIZE:-50M} - # PHP_POST_MAX_SIZE: ${PHP_POST_MAX_SIZE:-50M} - - # # PHP-FPM Configuration - # PHP_FPM_PM: ${PHP_FPM_PM:-dynamic} - # PHP_FPM_PM_MAX_CHILDREN: ${PHP_FPM_PM_MAX_CHILDREN:-50} - # PHP_FPM_PM_START_SERVERS: ${PHP_FPM_PM_START_SERVERS:-5} - # PHP_FPM_PM_MIN_SPARE_SERVERS: ${PHP_FPM_PM_MIN_SPARE_SERVERS:-5} - # PHP_FPM_PM_MAX_SPARE_SERVERS: ${PHP_FPM_PM_MAX_SPARE_SERVERS:-10} - # PHP_FPM_PM_MAX_REQUESTS: ${PHP_FPM_PM_MAX_REQUESTS:-500} - - # # Nginx Configuration - # NGINX_WORKER_PROCESSES: ${NGINX_WORKER_PROCESSES:-auto} - # NGINX_WORKER_CONNECTIONS: ${NGINX_WORKER_CONNECTIONS:-1024} - # NGINX_CLIENT_MAX_BODY_SIZE: ${NGINX_CLIENT_MAX_BODY_SIZE:-100M} - # NGINX_KEEPALIVE_TIMEOUT: ${NGINX_KEEPALIVE_TIMEOUT:-65} - - # # Composer - # COMPOSER_MEMORY_LIMIT: ${COMPOSER_MEMORY_LIMIT:--1} - # COMPOSER_HOME: ${COMPOSER_HOME:-/root/.composer} - - # # Xdebug - # XDEBUG_MODE: ${XDEBUG_MODE:-off} - # XDEBUG_CLIENT_HOST: ${XDEBUG_CLIENT_HOST:-host.docker.internal} - # XDEBUG_CLIENT_PORT: ${XDEBUG_CLIENT_PORT:-9003} - # XDEBUG_SESSION: ${XDEBUG_SESSION:-PHPSTORM} - - # # OPcache - # OPCACHE_ENABLE: ${OPCACHE_ENABLE:-1} - # OPCACHE_VALIDATE_TIMESTAMPS: ${OPCACHE_VALIDATE_TIMESTAMPS:-1} - # OPCACHE_REVALIDATE_FREQ: ${OPCACHE_REVALIDATE_FREQ:-2} - - # # Features - # DEMO_MODE: ${DEMO_MODE:-false} - # HEALTH_CHECK_INSTALL: ${HEALTH_CHECK_INSTALL:-true} - - # # Timezone - # TZ: ${TZ:-UTC} - - # # Logging - # LOG_LEVEL: ${LOG_LEVEL:-info} - - # Network configuration - networks: - kariricode_network: - aliases: - - php-service - - api-server - - # DNS configuration - dns: - - 8.8.8.8 - - 8.8.4.4 - - # Extra hosts for host machine communication - extra_hosts: - - "host.docker.internal:host-gateway" - - # Health check - healthcheck: - test: ["CMD-SHELL", "curl -f http://localhost/health || exit 1"] - interval: ${HEALTHCHECK_INTERVAL:-30s} - timeout: ${HEALTHCHECK_TIMEOUT:-10s} - retries: ${HEALTHCHECK_RETRIES:-3} - start_period: ${HEALTHCHECK_START_PERIOD:-40s} - - # Logging configuration - logging: - driver: "json-file" - options: - max-size: "${LOG_MAX_SIZE:-10m}" - max-file: "${LOG_MAX_FILE:-3}" - compress: "true" - labels: "service,environment" - - # Labels for organization and monitoring - labels: - com.kariricode.service: "php-api" - com.kariricode.environment: "${APP_ENV:-development}" - com.kariricode.version: "${APP_VERSION:-dev}" - com.kariricode.description: "PHP-FPM + Nginx + Redis service (kariricode/php-api-stack)" - com.kariricode.maintainer: "devops@kariricode.org" - - # Dependencies with health check conditions - depends_on: - memcached: - condition: service_healthy - restart: true - - # Security options - security_opt: - - no-new-privileges:true - - # Capability management (drop all, add only needed) - cap_drop: - - ALL - cap_add: - - CHOWN - - SETGID - - SETUID - - NET_BIND_SERVICE - - DAC_OVERRIDE - - # Profiles for different environments - profiles: - - development - - production - - testing - - # ============================================================================ - # Memcached Service - # ============================================================================ - memcached: - image: memcached:${MEMCACHED_VERSION:-1.6-alpine} - container_name: ${APP_NAME:-kariricode-devkit}_memcached - hostname: memcached-cache - - # Restart policy - restart: unless-stopped - - # Command with tuned parameters - command: > - memcached - -m ${MEMCACHED_MEMORY:-256} - -c ${MEMCACHED_MAX_CONNECTIONS:-1024} - -t ${MEMCACHED_THREADS:-4} - -I ${MEMCACHED_MAX_ITEM_SIZE:-5m} - -v - - # Resource limits - deploy: - resources: - limits: - cpus: "${MEMCACHED_CPU_LIMIT:-1.0}" - memory: ${MEMCACHED_MEMORY_TOTAL:-512M} - reservations: - cpus: "${MEMCACHED_CPU_RESERVATION:-0.25}" - memory: ${MEMCACHED_MEMORY_RESERVATION:-256M} - - # Port mapping - ports: - - target: 11211 - published: ${MEMCACHED_PORT:-11211} - protocol: tcp - mode: host - - # Network - networks: - kariricode_network: - aliases: - - cache-service - - # Health check with proper validation - healthcheck: - test: ["CMD", "nc", "-z", "127.0.0.1", "11211"] - interval: ${MEMCACHED_HEALTHCHECK_INTERVAL:-10s} - timeout: ${MEMCACHED_HEALTHCHECK_TIMEOUT:-5s} - retries: ${MEMCACHED_HEALTHCHECK_RETRIES:-3} - start_period: ${MEMCACHED_HEALTHCHECK_START_PERIOD:-10s} - - # Logging - logging: - driver: "json-file" - options: - max-size: "${LOG_MAX_SIZE:-10m}" - max-file: "${LOG_MAX_FILE:-3}" - compress: "true" - - # Labels - labels: - com.kariricode.service: "memcached" - com.kariricode.environment: "${APP_ENV:-development}" - com.kariricode.description: "Memcached caching service" - com.kariricode.maintainer: "devops@kariricode.org" - - # Security - security_opt: - - no-new-privileges:true - cap_drop: - - ALL - cap_add: - - SETGID - - SETUID - - # User (non-root) - user: "memcache" - - # Read-only root filesystem (security hardening) - read_only: true - tmpfs: - - /tmp:size=50M,mode=1777 - - # Profiles - profiles: - - development - - production - - testing - -# ============================================================================== -# Networks -# ============================================================================== -networks: - kariricode_network: - driver: bridge - name: ${APP_NAME:-kariricode-devkit}_network - - # Enable IPv6 if needed - enable_ipv6: ${ENABLE_IPV6:-false} - - # Network configuration - driver_opts: - com.docker.network.bridge.name: ${BRIDGE_NAME:-kariricode0} - com.docker.network.bridge.enable_icc: "true" - com.docker.network.bridge.enable_ip_masquerade: "true" - com.docker.network.driver.mtu: ${NETWORK_MTU:-1500} - - # IPAM configuration - ipam: - driver: default - config: - - subnet: ${NETWORK_SUBNET:-172.20.0.0/16} - gateway: ${NETWORK_GATEWAY:-172.20.0.1} - - # Labels - labels: - com.kariricode.network: "main" - com.kariricode.environment: "${APP_ENV:-development}" - -# ============================================================================== -# Volumes -# ============================================================================== -volumes: - # Composer cache for faster dependency installation - composer-cache: - name: ${APP_NAME:-kariricode-devkit}_composer_cache - driver: local - labels: - com.kariricode.volume: "composer-cache" - com.kariricode.description: "Composer dependencies cache" - - # PHPStan cache for faster static analysis - phpstan-cache: - name: ${APP_NAME:-kariricode-devkit}_phpstan_cache - driver: local - labels: - com.kariricode.volume: "phpstan-cache" - com.kariricode.description: "PHPStan analysis cache" - - # PHP session storage - php-session: - name: ${APP_NAME:-kariricode-devkit}_php_session - driver: local - labels: - com.kariricode.volume: "php-session" - com.kariricode.description: "PHP session data" - -# ============================================================================== -# Extension Fields (DRY - reusable configurations) -# ============================================================================== -x-logging: &default-logging - driver: "json-file" - options: - max-size: "10m" - max-file: "3" - compress: "true" - -x-healthcheck-defaults: &healthcheck-defaults - interval: 30s - timeout: 10s - retries: 3 - start_period: 40s - -x-security-defaults: &security-defaults - security_opt: - - no-new-privileges:true - cap_drop: - - ALL -# ============================================================================== -# Profiles Documentation -# ============================================================================== -# -# Available profiles: -# - development: Full dev stack with Xdebug and verbose logging -# - production: Optimized for production with minimal logging -# - testing: Isolated testing environment -# -# Usage examples: -# make up-safe # Development (recommended) -# docker compose --profile development up -# docker compose --profile production up -d -# docker compose --profile testing run php vendor/bin/phpunit -# -# Troubleshooting: -# make diagnose-ports # Check port conflicts -# make status # Service status -# make logs SERVICE=php # View logs -# make shell # Enter PHP container -# -# ============================================================================== diff --git a/docs/BUILDING.md b/docs/BUILDING.md new file mode 100644 index 0000000..c150e12 --- /dev/null +++ b/docs/BUILDING.md @@ -0,0 +1,185 @@ +# Building kcode.phar + +This document covers how to compile `kcode.phar` from source, verify the artifact, and automate releases. + +## Prerequisites + +| Requirement | Minimum Version | Purpose | +|---|---|---| +| PHP | 8.4+ | Runtime and PHAR compilation | +| Composer | 2.x | Dependency installation | + +### PHP Configuration + +PHAR compilation requires `phar.readonly=0`. The Makefile and `bin/build-phar.php` pass this automatically. For manual builds: + +```bash +php -d phar.readonly=0 bin/build-phar.php +``` + +Alternatively, set it in `php.ini`: + +```ini +phar.readonly = Off +``` + +## Building + +### Via Makefile (recommended) + +```bash +# Full release pipeline: quality checks → build → verify +make release + +# Or step by step: +make install # Install dependencies +make build # Compile kcode.phar +make verify # Verify integrity +make self-test # Run against this project +``` + +### Manual Build + +```bash +# 1. Install dependencies (dev tools are bundled in the PHAR) +composer install --no-interaction --prefer-dist --optimize-autoloader --no-scripts + +# 2. Compile PHAR using the native builder +php -d phar.readonly=0 bin/build-phar.php + +# 3. Verify +php build/kcode.phar --version +php build/kcode.phar --help +``` + +## PHAR Builder — `bin/build-phar.php` + +The project uses a native PHP PHAR builder (`bin/build-phar.php`) instead of `humbug/box`. + +**Why:** Box 4.x has a known compatibility issue with PHP 8.4 (`chdir(): Not a directory (errno 20)` during `endBuffering()`). The native builder avoids this bug entirely with no external dependency. + +**What it does:** +1. Collects all `src/` PHP files (38 files) +2. Collects `vendor/` PHP + JSON files, excluding test/doc directories +3. Adds `LICENSE` +4. Sets `bin/kcode` as the entry-point stub +5. GZ-compresses the archive +6. Sets permissions to `0755` + +```bash +# Output +📦 Building kcode.phar... + + src/: 38 PHP files + + vendor/: files + + LICENSE + + stub (bin/kcode entry point) + +✅ Built: build/kcode.phar (X.XX MB) + Files: +``` + +## Build Output + +``` +build/ +└── kcode.phar # GZ compressed PHAR +``` + +The PHAR includes: + +| Content | Source | +|---|---| +| Devkit source | `src/` (38 PHP files) | +| Entry point | `bin/kcode` | +| PHPUnit | `vendor/phpunit/` + transitive deps | +| PHPStan | `vendor/phpstan/` + transitive deps | +| PHP-CS-Fixer | `vendor/friendsofphp/` + transitive deps | +| Rector | `vendor/rector/` + transitive deps | +| Psalm | `vendor/vimeo/` + transitive deps | +| Autoloader | `vendor/autoload.php` + `vendor/composer/` | +| License | `LICENSE` | + +## Verification + +After building, verify the PHAR works correctly: + +```bash +# Version check +php build/kcode.phar --version +# → KaririCode Devkit 1.0.0 + +# Help output +php build/kcode.phar --help +# → Shows all 10 commands + +# Self-test against a real project +cd /path/to/kariricode-component +php /path/to/kcode.phar init +php /path/to/kcode.phar quality +``` + +### PHAR Signature + +```bash +php -r "echo (new Phar('build/kcode.phar'))->getSignature()['hash'];" +``` + +## Distribution + +### GitHub Releases + +The recommended distribution method. See `.github/workflows/release.yml`: + +1. Tag a release: `git tag v1.0.0 && git push --tags` +2. CI compiles the PHAR and attaches it to the GitHub release. +3. Users download via: + +```bash +wget https://github.com/kariricode/devkit/releases/latest/download/kcode.phar +chmod +x kcode.phar +sudo mv kcode.phar /usr/local/bin/kcode +``` + +### Self-Update (Future) + +A `kcode self-update` command is planned for v1.1 to download the latest PHAR from GitHub releases. + +## Troubleshooting + +### `phar.readonly = On` + +``` +Creating a phar archive is disabled by the php.ini setting phar.readonly +``` + +**Fix:** Pass `-d phar.readonly=0` to PHP or set `phar.readonly = Off` in php.ini. + +### PHAR too large + +If the PHAR exceeds 30 MB: + +1. Check that GZ compression is enabled in `bin/build-phar.php` (`Phar::GZ`). +2. Verify test/doc directories are excluded by the builder's `$excludeDirs` list. + +### Binary not found inside PHAR + +If `kcode.phar test` reports "Binary not found for phpunit": + +1. Verify dependencies were installed before building: `composer install` +2. Check that `vendor/bin/phpunit` exists before compilation. + +### Platform-specific issues + +The PHAR uses `#!/usr/bin/env php` as the shebang. On systems where PHP is not in PATH: + +```bash +php kcode.phar quality # Explicit PHP invocation +``` + +## Version Bumping + +The version is stored in one place: + +1. `src/Core/Devkit.php` → `private const string VERSION = '1.0.0';` + +The Makefile resolves the version via `git describe --tags --abbrev=0`, falling back to `'dev'`. Always tag releases with `git tag vX.Y.Z` before running `make release`. diff --git a/docs/INDEX.md b/docs/INDEX.md new file mode 100644 index 0000000..aa90182 --- /dev/null +++ b/docs/INDEX.md @@ -0,0 +1,37 @@ +# KaririCode Devkit — Documentation Index + +Documentation for the **kariricode/devkit** package — the unified quality toolchain for the KaririCode Framework ecosystem. + +--- + +## Architecture Decision Records + +| ADR | Title | Status | +|---|---|---| +| [ADR-001](adr/ADR-001-phar-distribution.md) | PHAR-Based Distribution Strategy | Accepted | +| [ADR-002](adr/ADR-002-zero-dependencies.md) | Zero External Dependencies in Core | Accepted | +| [ADR-003](adr/ADR-003-config-generation.md) | Configuration Generation Over Manual Configuration | Accepted | +| [ADR-004](adr/ADR-004-binary-resolution.md) | Three-Tier Binary Resolution Strategy | Accepted | +| [ADR-005](adr/ADR-005-kariricode-directory.md) | Centralized `.kcode/` Directory Convention | Accepted | +| [ADR-006](adr/ADR-006-immutable-value-objects.md) | Immutable Value Objects for Tool Results | Accepted | + +## Specifications + +| Spec | Title | Version | +|---|---|---| +| [SPEC-001](spec/SPEC-001-project-detection.md) | Project Detection and Configuration Merging | 1.0.0 | +| [SPEC-002](spec/SPEC-002-cli-interface.md) | CLI Command Interface and Execution Pipeline | 1.0.0 | +| [SPEC-003](spec/SPEC-003-tool-runner.md) | Tool Runner Abstraction and Process Execution | 1.0.0 | + +## Quick Navigation + +| Document | Description | +|---|---| +| [README](../README.md) | Installation, usage, CLI reference, architecture | +| [BUILDING.md](BUILDING.md) | PHAR compilation, troubleshooting, release automation | +| [CHANGELOG](../CHANGELOG.md) | Release history and migration notes | +| [composer.json](../composer.json) | Package definition and Composer scripts | +| [Makefile](../Makefile) | Build automation (`make help` for all targets) | +| [ci.yml](../.github/workflows/ci.yml) | Quality pipeline on push/PR | +| [code-quality.yml](../.github/workflows/code-quality.yml) | Security, PHPStan, CS-Fixer via kcode CLI | +| [release.yml](../.github/workflows/release.yml) | Automated PHAR release on tag push | diff --git a/docs/adr/ADR-001-phar-distribution.md b/docs/adr/ADR-001-phar-distribution.md new file mode 100644 index 0000000..bf63102 --- /dev/null +++ b/docs/adr/ADR-001-phar-distribution.md @@ -0,0 +1,76 @@ +# ADR-001: PHAR-Based Distribution Strategy + +**Status:** Accepted +**Date:** 2025-02-28 +**Deciders:** Walmir Silva +**Context:** KaririCode Framework Devkit v1.0.0 + +## Context + +The KaririCode Framework comprises 35+ components, each requiring identical development tooling: PHPUnit, PHPStan, PHP-CS-Fixer, Rector, and Psalm. The conventional Composer `require-dev` approach results in: + +- **175+ redundant dependency entries** across the ecosystem (5 tools × 35+ components). +- **~120 MB per component** in `vendor/` from dev dependencies alone. +- **Inconsistent tool versions** when components are updated at different cadences. +- **5+ config files per project root** (phpunit.xml.dist, phpstan.neon, etc.) with near-identical content. + +## Decision + +Distribute the devkit as a **PHAR archive** compiled via [humbug/box](https://github.com/box-project/box), bundling all five quality tools and their transitive dependencies into a single `kcode.phar` file (~15-20 MB). + +Additionally, support **Composer library mode** (`composer require --dev kariricode/devkit`) for environments where PHAR is impractical (e.g., CI caches, Dependabot). + +## Rationale + +### PHAR Advantages + +1. **Single artifact** — One file replaces 5 `require-dev` entries and all their transitive dependencies. +2. **Version pinning** — The PHAR freezes exact tool versions. Every component uses the same PHPStan 2.x, same Rector 2.x, etc. +3. **Zero-conflict installation** — PHAR runs in an isolated class-loading context. No dependency conflicts between the project's production code and the analysis tools. +4. **Portable** — A CI pipeline can `wget` the PHAR and run it without `composer install` for dev dependencies. + +### Composer Library Fallback + +The `bin/kcode` entry point resolves autoloaders in priority order: + +``` +1. PHAR-internal vendor/autoload.php +2. Project-local vendor/autoload.php (Composer require-dev) +3. Global Composer vendor/autoload.php +``` + +This makes the package usable in both distribution modes without code changes. + +### Alternatives Considered + +| Alternative | Rejected Because | +|---|---| +| Composer plugin | Couples to Composer lifecycle; does not solve version consistency | +| Docker image | Adds container overhead; poor IDE integration | +| Makefile + global tools | No version pinning; system-dependent | +| Mono-repo shared config | Doesn't scale to independently versioned components | + +## Consequences + +### Positive + +- Component `composer.json` shrinks from 5+ dev dependencies to 1 (or 0 with PHAR). +- Tool version upgrades happen once in devkit, propagate to all components on next PHAR release. +- CI pipelines download one artifact instead of resolving 5 dependency trees. + +### Negative + +- PHAR compilation requires `humbug/box` and a release pipeline. +- PHAR file size (~15-20 MB) must be managed; GZ compression mitigates this. +- Developers must update the PHAR when tool versions change (mitigated by Composer fallback). + +### Risks + +- **Tool compatibility** — A bundled tool version may conflict with a component's minimum PHP version. Mitigated by targeting PHP 8.4+ across the ecosystem. +- **PHAR readonly** — PHP `phar.readonly=1` (default) prevents runtime modification. This is acceptable since the PHAR is read-only by design. + +## References + +- [humbug/box documentation](https://github.com/box-project/box/blob/main/doc/configuration.md) +- [PHP PHAR extension](https://www.php.net/manual/en/book.phar.php) +- [Composer bin-dir specification](https://getcomposer.org/doc/articles/vendor-binaries.md) diff --git a/docs/adr/ADR-002-zero-dependencies.md b/docs/adr/ADR-002-zero-dependencies.md new file mode 100644 index 0000000..dc7245a --- /dev/null +++ b/docs/adr/ADR-002-zero-dependencies.md @@ -0,0 +1,86 @@ +# ADR-002: Zero External Dependencies in Core + +**Status:** Accepted +**Date:** 2025-02-28 +**Deciders:** Walmir Silva +**Context:** KaririCode Framework Devkit v1.0.0 + +## Context + +The devkit's `src/` layer (Contract, Core, Command, Runner, Configuration, Exception, ValueObject) needs a CLI framework, process execution, and config generation. Standard PHP ecosystem choices include Symfony Console (~50+ classes), League CLImate, or Laravel Artisan. + +KaririCode Framework follows a **zero external dependencies** principle (ARFA 1.3, §2.1) for all framework components. + +## Decision + +The devkit core (`src/`) has **zero external `require` dependencies**. The only PHP requirement is `>=8.4`. + +All infrastructure is implemented in-house: + +| Capability | Implementation | Lines | +|---|---|---| +| CLI router | `Command\Application` | 118 | +| Argument parsing | `Command\AbstractCommand` | 113 | +| Process execution | `Core\ProcessExecutor` | 109 | +| Config file loading | `Core\DevkitConfig` | 112 | +| ANSI output formatting | Inline escape sequences | ~20 | + +The five quality tools (PHPUnit, PHPStan, etc.) are `require-dev` dependencies — they are not imported at the PHP level. The devkit spawns them as subprocesses via `proc_open()`. + +## Rationale + +### Why Not Symfony Console + +Symfony Console is excellent but introduces: + +- **~50 classes** and their autoloading overhead into the PHAR. +- **Transitive dependencies** (symfony/string, symfony/deprecation-contracts, etc.). +- **PHAR namespace conflicts** with projects that use different Symfony versions. +- **Conceptual overhead** — the devkit has exactly 9 commands with simple flag parsing. A full command framework is over-engineered. + +### What 231 Lines Buys + +`Application` (118) + `AbstractCommand` (113) = 231 lines that provide: + +- Named command dispatch with `--help` and `--version`. +- ANSI-colored output (info, warning, error, banner). +- Flag detection (`--coverage`, `--check`). +- Key-value option extraction (`--suite=Unit`). +- Passthrough argument filtering. + +This covers 100% of the devkit's CLI needs with no runtime dependencies and sub-millisecond boot time. + +### Process Execution via proc_open + +Tools are spawned as child processes, not loaded as PHP libraries. This provides: + +- **Isolation** — Tool crashes don't bring down the devkit process. +- **Output capture** — stdout and stderr are captured separately into `ToolResult`. +- **Timing** — `hrtime(true)` captures nanosecond-precision execution time. +- **Exit code semantics** — Standard Unix exit codes propagate through `ToolResult::$exitCode`. + +## Consequences + +### Positive + +- PHAR boot time < 1ms (no autoloader for framework classes beyond the devkit itself). +- Zero risk of dependency conflicts with host projects. +- Full control over CLI behavior — no upstream breaking changes. +- Entire CLI layer is readable in ~15 minutes. + +### Negative + +- No built-in features like table rendering, progress bars, or interactive prompts. +- Argument parsing is less sophisticated than Symfony Console's InputDefinition. +- Maintenance burden for CLI infrastructure falls on the project. + +### Acceptable Trade-offs + +- Table rendering is unnecessary — the devkit outputs tool stdout directly. +- Progress bars are unnecessary — tool progress is handled by the tools themselves. +- Interactive prompts are unnecessary — the devkit is designed for CI and scripted use. + +## References + +- ARFA 1.3 Specification, §2.1: Zero External Dependencies Principle +- KaririCode Specification V4.0, §3.2: Package Independence diff --git a/docs/adr/ADR-003-config-generation.md b/docs/adr/ADR-003-config-generation.md new file mode 100644 index 0000000..8f98117 --- /dev/null +++ b/docs/adr/ADR-003-config-generation.md @@ -0,0 +1,88 @@ +# ADR-003: Configuration Generation Over Manual Configuration + +**Status:** Accepted +**Date:** 2025-02-28 +**Deciders:** Walmir Silva +**Context:** KaririCode Framework Devkit v1.0.0 + +## Context + +Each quality tool requires a configuration file (phpunit.xml.dist, phpstan.neon, php-cs-fixer.php, rector.php, psalm.xml). Across 35+ components, these files are near-identical, differing only in: + +- Source directory paths (derived from PSR-4 autoload) +- Test suite directory structure +- PHP version target +- PHPStan analysis level + +Maintaining 175+ config files manually leads to configuration drift, inconsistent rules, and merge-conflict friction when updating standards. + +## Decision + +Config files are **generated deterministically** from a `ProjectContext` snapshot by `ConfigGenerator` implementations. The generation cycle is: + +``` +composer.json → ProjectDetector → ProjectContext → ConfigGenerator → .kcode/*.config +``` + +Manual customization is achieved through a single override file (`devkit.php`) that is **merged** with ecosystem defaults — not by editing generated configs. + +Generated files include a header comment: `Generated by KaririCode Devkit — override via devkit.php (project root)`. + +## Rationale + +### Generation Advantages + +1. **Single source of truth** — Coding standards and analysis rules live in `ProjectDetector::DEFAULT_CS_RULES` and `DEFAULT_RECTOR_SETS`. Updating the devkit updates all components. +2. **Deterministic output** — Same `ProjectContext` always produces the same config files. No hidden state, no manual edits to preserve. +3. **Override granularity** — `devkit.php` supports per-project customization at the key level (PHPStan level, extra CS rules, excluded directories) without duplicating the entire config. +4. **Safe regeneration** — `kcode init` can be run repeatedly. Generated files are disposable artifacts. + +### Override Merging Strategy + +```php +// devkit.php — only specify what differs from defaults + 8, // override: lower level + 'exclude_dirs' => ['src/Contract', 'src/Legacy'], // override: extra exclusion + 'cs_fixer_rules' => ['yoda_style' => false], // merge: added to defaults +]; +``` + +The merge strategy varies by key type: + +| Key | Strategy | Rationale | +|---|---|---| +| Scalar (phpstan_level, php_version) | Replace | Project-specific requirement | +| List (source_dirs, exclude_dirs) | Replace | Full override for clarity | +| Map (cs_fixer_rules) | array_merge | Additive customization | +| Map (test_suites) | Replace | Suite structure is project-specific | + +### Alternatives Considered + +| Alternative | Rejected Because | +|---|---| +| Symlinks to shared configs | Doesn't support per-project overrides | +| Composer scripts + templates | Requires Twig/Blade; adds dependencies | +| .dist files with manual copy | Config drift returns immediately | +| Central config repo + git submodule | Poor developer experience; merge conflicts | + +## Consequences + +### Positive + +- Zero config drift across the ecosystem. +- One-command setup for new components: `kcode init`. +- Override file is version-controlled alongside the project. +- Generated configs are gitignored-friendly (optional). + +### Negative + +- Developers cannot hand-edit generated configs (edits are overwritten on next `init`). +- The override merge logic must be well-documented to avoid confusion. +- Adding new config keys requires a devkit release. + +## References + +- Microsoft documentation generators: config-as-code principle +- Terraform HCL: override files merged with base configuration +- ARFA 1.3 Specification, §4.3: Deterministic Configuration diff --git a/docs/adr/ADR-004-binary-resolution.md b/docs/adr/ADR-004-binary-resolution.md new file mode 100644 index 0000000..cd95b17 --- /dev/null +++ b/docs/adr/ADR-004-binary-resolution.md @@ -0,0 +1,77 @@ +# ADR-004: Three-Tier Binary Resolution Strategy + +**Status:** Accepted +**Date:** 2025-02-28 +**Deciders:** Walmir Silva +**Context:** KaririCode Framework Devkit v1.0.0 + +## Context + +The devkit must locate tool binaries (phpunit, phpstan, php-cs-fixer, rector, psalm, composer) at runtime. The installation context varies: + +1. **PHAR distribution** — Tools are bundled inside the archive. +2. **Composer dependency** — Tools are in the project's `vendor/bin/`. +3. **Global installation** — Tools are installed system-wide via `composer global require` or package managers. + +A single resolution strategy cannot serve all contexts. + +## Decision + +`ProcessExecutor::resolveBinary()` implements a three-tier cascade: + +``` +Tier 1: PHAR-internal → Phar::running(true) . '/' . $vendorBin +Tier 2: Project-local → $workingDirectory . '/' . $vendorBin +Tier 3: Global PATH → shell_exec('command -v $basename') +``` + +Resolution stops at the first match. If no tier resolves, `null` is returned and `AbstractToolRunner::run()` produces a `ToolResult` with exit code 127. + +### Exception: ComposerAuditRunner + +Composer is typically installed globally, not in `vendor/bin/`. `ComposerAuditRunner` overrides `binary()` to check global PATH first (Tier 3 before Tier 2). + +## Rationale + +### Tier Priority + +| Tier | Context | Priority Justification | +|---|---|---| +| 1. PHAR-internal | Self-contained distribution | Guarantees exact version match | +| 2. Project vendor | Composer require-dev | Respects project-specific version constraints | +| 3. Global PATH | System-wide tools | Fallback for minimal setups | + +### Why Not Rely on PATH Alone + +PATH resolution provides no version guarantees. A globally installed PHPStan 1.x would be used even if the devkit expects 2.x. PHAR-internal binaries eliminate this risk entirely. + +### Binary Caching + +`AbstractToolRunner` caches the resolved binary path in `$resolvedBinary` (null-coalescing assignment). Resolution happens once per runner instance per process. Since the CLI is short-lived (single command execution), this is sufficient without cache invalidation. + +### Security + +Tier 3 uses `escapeshellarg()` around the binary basename to prevent shell injection: + +```php +shell_exec('command -v ' . \escapeshellarg($basename) . ' 2>/dev/null') +``` + +## Consequences + +### Positive + +- PHAR users get deterministic tool versions with zero configuration. +- Composer users can override tool versions via their own `require-dev`. +- Global fallback enables `kcode` usage in minimal CI environments. + +### Negative + +- Tier precedence may surprise developers who expect their global tool to be used over the PHAR-bundled version. +- `command -v` is POSIX-specific; Windows support would require `where.exe` (out of scope for v1.0). + +## References + +- POSIX `command -v` specification: IEEE Std 1003.1-2017 +- PHP `Phar::running()` documentation +- Composer `vendor/bin` binary proxy specification diff --git a/docs/adr/ADR-005-kariricode-directory.md b/docs/adr/ADR-005-kariricode-directory.md new file mode 100644 index 0000000..1c8ef1e --- /dev/null +++ b/docs/adr/ADR-005-kariricode-directory.md @@ -0,0 +1,153 @@ +# ADR-005: Centralized .kcode/ Directory Convention + +**Status:** Accepted +**Date:** 2025-02-28 +**Deciders:** Walmir Silva +**Context:** KaririCode Framework Devkit v1.0.0 + +## Context + +Quality tools traditionally place their config files in the project root: + +``` +project/ +├── phpunit.xml.dist +├── phpstan.neon +├── .php-cs-fixer.dist.php +├── rector.php +├── psalm.xml +├── composer.json +├── README.md +└── src/ +``` + +Five config files in the root creates visual noise and pushes domain-relevant files below the fold in directory listings. Each file also requires a `.gitignore` entry for its cache directory. + +## Decision + +All devkit-generated configs and build artifacts are placed inside a single `.kcode/` directory: + +``` +project/ +├── devkit.php # Optional: project-specific overrides (committed to git) +├── .kcode/ # Gitignored — regenerated by `kcode init` +│ ├── phpunit.xml.dist # Generated +│ ├── phpstan.neon # Generated +│ ├── php-cs-fixer.php # Generated +│ ├── rector.php # Generated +│ ├── psalm.xml # Generated +│ └── build/ # Caches, coverage, reports +│ ├── .phpunit.cache/ +│ ├── .phpstan/ +│ ├── .php-cs-fixer.cache +│ ├── .psalm/ +│ ├── coverage/ +│ ├── clover.xml +│ └── junit.xml +├── composer.json +├── README.md +└── src/ +``` + +### Gitignore Strategy + +`kcode init` adds `.kcode/` to `.gitignore` automatically: + +```gitignore +# KaririCode Devkit — generated configs and build artifacts +.kcode/ +``` + +The entire `.kcode/` directory is gitignored because: + +1. **Configs are deterministic** — generated from `composer.json` + `devkit.php`. Running `kcode init` on any machine produces identical output. +2. **Zero CI friction** — CI already runs `kcode init` before quality checks. No stale config drift between branches. +3. **Clean diffs** — config regeneration after devkit upgrades doesn't pollute PRs. + +If a project needs custom overrides, create `devkit.php` in the project root: + +```bash +kcode init --config # Scaffolds devkit.php with all available keys documented +``` + +The `devkit.php` lives at the project root (not inside `.kcode/`) and is committed to git normally. + +## Rationale + +### Clean Project Root + +The project root now contains only domain-relevant files. The `.kcode/` directory is a single entry that collapses in file explorers and IDE project trees. + +### Convention Over Configuration + +The directory name `.kcode/` is: + +- **Dot-prefixed** — Hidden in Unix directory listings by default. +- **Namespaced** — No collision with other tools (unlike `.config/` or `.tools/`). +- **Discoverable** — Any KaririCode contributor recognizes the convention. + +### Relative Path Strategy + +Generated configs use `../` relative paths to reference project source: + +```xml + +../tests/Unit +``` + +```neon +# .kcode/phpstan.neon +paths: + - ../src +``` + +This works because tools are invoked with `--configuration .kcode/phpunit.xml.dist`, making the config file's directory the resolution base. + +### Build Isolation + +All tool caches and generated reports go to `.kcode/build/`: + +| Tool | Cache Location | +|---|---| +| PHPUnit | `.kcode/build/.phpunit.cache/` | +| PHPStan | `.kcode/build/.phpstan/` | +| PHP-CS-Fixer | `.kcode/build/.php-cs-fixer.cache` | +| Psalm | `.kcode/build/.psalm/` | +| Coverage HTML | `.kcode/build/coverage/` | +| JUnit XML | `.kcode/build/junit.xml` | +| Clover XML | `.kcode/build/clover.xml` | + +One `.gitignore` entry covers all build artifacts. + +## Consequences + +### Positive + +- Clean project root — 5 fewer files visible at the top level. +- Single gitignore entry (`.kcode/`) covers all generated configs and build artifacts. +- No config drift — regenerated deterministically from `composer.json` on every `kcode init`. +- Clean PRs — devkit version upgrades don't pollute diffs with config changes. +- Convention is immediately recognizable across the KaririCode ecosystem. + +### Negative + +- CI pipelines must run `kcode init` before any quality step. The standard CI workflow already includes this. +- Tool commands must specify config paths explicitly (`--configuration .kcode/phpunit.xml.dist`). The devkit CLI handles this transparently, but raw tool invocation requires the path. +- IDE integrations (PHPStorm PHPUnit runner) may need manual config path setup. + +### Migration + +Projects migrating from root-level configs: + +```bash +composer require --dev kariricode/devkit +vendor/bin/kcode init # Generates .kcode/, adds to .gitignore +vendor/bin/kcode migrate # Interactive removal of old deps and configs +``` + +## References + +- Angular CLI: `.angular/` directory convention +- Next.js: `.next/` build directory +- Rust Cargo: `target/` build directory +- ARFA 1.3 Specification, §5.1: Project Layout Conventions diff --git a/docs/adr/ADR-006-immutable-value-objects.md b/docs/adr/ADR-006-immutable-value-objects.md new file mode 100644 index 0000000..6118091 --- /dev/null +++ b/docs/adr/ADR-006-immutable-value-objects.md @@ -0,0 +1,102 @@ +# ADR-006: Immutable Value Objects for Tool Results + +**Status:** Accepted +**Date:** 2025-02-28 +**Deciders:** Walmir Silva +**Context:** KaririCode Framework Devkit v1.0.0 + +## Context + +Tool execution produces structured output: exit code, stdout, stderr, elapsed time, and a pass/fail determination. This data flows from `ProcessExecutor` → `ToolRunner` → `Command` → user output, and is also aggregated in quality pipeline reports. + +The data must be reliable at every consumption point — no mutation between capture and display. + +## Decision + +Tool execution results are modeled as `final readonly class` value objects (PHP 8.2+): + +```php +final readonly class ToolResult +{ + public bool $success; + + public function __construct( + public string $toolName, + public int $exitCode, + public string $stdout, + public string $stderr, + public float $elapsedSeconds, + ) { + $this->success = 0 === $exitCode; + } +} +``` + +```php +final readonly class QualityReport +{ + public bool $passed; + public float $totalSeconds; + public int $failureCount; + + public function __construct( + public array $results, // list + ) { + $this->passed = array_all($results, fn (ToolResult $r) => $r->success); + // ... aggregations computed in constructor + } +} +``` + +`ProjectContext` follows the same pattern — `final readonly class` with all state computed at construction time. + +## Rationale + +### ARFA 1.3 Principle P1: Immutable State + +ARFA 1.3 mandates immutable state for data flowing through processing pipelines. Value objects satisfy this by construction: + +- `readonly` prevents property reassignment after construction. +- `final` prevents subclassing that could introduce mutation. +- Derived properties (`$success`, `$passed`, `$totalSeconds`) are computed once in the constructor. + +### Why Not DTOs with Getters + +KaririCode V4.0 Specification explicitly forbids the getter/setter anti-pattern. Public readonly properties are the canonical access pattern: + +```php +// ✅ Direct property access +$result->exitCode +$result->success + +// ❌ Getter anti-pattern (V4.0 violation) +$result->getExitCode() +$result->isSuccess() +``` + +### Constructor-Computed Derived State + +`QualityReport::$passed` and `$failureCount` are derived from `$results`. Computing them in the constructor guarantees consistency — there is no window where the report exists but aggregations haven't been calculated. + +This follows the **complete construction** principle: an object is fully valid immediately after `new`. + +## Consequences + +### Positive + +- Thread-safe by construction (relevant for future async/parallel tool execution). +- No defensive copying needed when passing results between layers. +- IDE autocompletion works directly on public properties. +- Memory-efficient — no getter overhead, no backing field duplication. + +### Negative + +- Cannot extend or decorate results without creating a new class. +- `array_all()` requires PHP 8.4+ (acceptable given the framework's minimum version). + +## References + +- ARFA 1.3 Specification, §2.3: Immutable State Principle (P1) +- KaririCode Specification V4.0, §4.1: No Getter/Setter Anti-pattern +- Martin Fowler, *Value Object* pattern (https://martinfowler.com/bliki/ValueObject.html) +- PHP RFC: Readonly classes (PHP 8.2) diff --git a/docs/spec/SPEC-001-project-detection.md b/docs/spec/SPEC-001-project-detection.md new file mode 100644 index 0000000..84b35c8 --- /dev/null +++ b/docs/spec/SPEC-001-project-detection.md @@ -0,0 +1,210 @@ +# SPEC-001: Project Detection and Configuration Merging + +**Version:** 1.0.0 +**Status:** Normative +**Date:** 2025-02-28 +**Author:** Walmir Silva + +## 1. Purpose + +This specification defines how the devkit detects project structure, loads configuration overrides, and produces the `ProjectContext` snapshot consumed by all generators and runners. + +## 2. Scope + +Covers `ProjectDetector`, `DevkitConfig`, and `ProjectContext` classes. Does not cover config file generation (see SPEC-003) or tool execution (see SPEC-002). + +## 3. Terminology + +| Term | Definition | +|---|---| +| **Project root** | Directory containing `composer.json` | +| **Devkit directory** | `{project_root}/.kcode/` | +| **Override file** | `devkit.php` (project root) — optional PHP file returning an associative array | +| **ProjectContext** | Immutable snapshot containing all resolved configuration values | + +## 4. Detection Pipeline + +### 4.1 Entry Point + +``` +ProjectDetector::detect(string $workingDirectory): ProjectContext +``` + +**Precondition:** `$workingDirectory` must be an absolute path. + +**Throws:** `DevkitException::projectNotDetected()` when `composer.json` is absent. + +### 4.2 Detection Sequence + +``` +1. Parse composer.json (JSON decode with JSON_THROW_ON_ERROR) +2. Load `devkit.php` from project root via DevkitConfig +3. For each configuration key: + a. Check devkit.php override + b. Fall back to composer.json detection + c. Fall back to ecosystem default +4. Construct ProjectContext (immutable) +``` + +### 4.3 Detection Rules + +#### 4.3.1 Project Name + +| Priority | Source | Example | +|---|---|---| +| 1 | `devkit.php → project_name` | `"kariricode/parser"` | +| 2 | `composer.json → name` | `"kariricode/parser"` | +| 3 | `basename($workingDirectory)` | `"parser"` | + +#### 4.3.2 Namespace + +| Priority | Source | Example | +|---|---|---| +| 1 | `devkit.php → namespace` | `"KaririCode\\Parser"` | +| 2 | First key in `autoload.psr-4` | `"KaririCode\\Parser\\"` → `"KaririCode\\Parser"` | +| 3 | Literal `"App"` | — | + +The trailing backslash from PSR-4 keys is stripped via `rtrim($ns, '\\')`. + +#### 4.3.3 PHP Version + +| Priority | Source | Example | +|---|---|---| +| 1 | `devkit.php → php_version` | `"8.4"` | +| 2 | First `\d+\.\d+` match in `require.php` | `">=8.4"` → `"8.4"` | +| 3 | Literal `"8.4"` | — | + +#### 4.3.4 Source Directories + +| Priority | Source | Result | +|---|---|---| +| 1 | `devkit.php → source_dirs` | Absolute paths from override | +| 2 | `autoload.psr-4` values | Absolute paths for existing directories | +| 3 | Fallback: `['src']` if directory exists | Single-element list | + +#### 4.3.5 Test Directories + +| Priority | Source | Result | +|---|---|---| +| 1 | `devkit.php → test_dirs` | Absolute paths from override | +| 2 | `autoload-dev.psr-4` values | Absolute paths for existing directories | +| 3 | Fallback: `['tests']` if directory exists | Single-element list | + +**Important:** Source and test fallbacks use distinct default directories (`src` vs `tests`) to prevent misidentification. + +#### 4.3.6 Test Suites + +| Priority | Source | +|---|---| +| 1 | `devkit.php → test_suites` | +| 2 | Auto-detected from test directory subdirectories | + +Auto-detection scans for standard suite names in order: `Unit`, `Integration`, `Conformance`, `Functional`. Each existing subdirectory becomes a named suite. + +If no standard subdirectories exist, a single `Default` suite is registered pointing to the first test directory. + +#### 4.3.7 PHPStan Level + +| Priority | Source | Default | +|---|---|---| +| 1 | `devkit.php → phpstan_level` | — | +| 2 | Ecosystem default | `9` (maximum) | + +#### 4.3.8 Psalm Level + +| Priority | Source | Default | +|---|---|---| +| 1 | `devkit.php → psalm_level` | — | +| 2 | Ecosystem default | `3` | + +#### 4.3.9 CS-Fixer Rules + +Override rules are **merged** with ecosystem defaults via `array_merge()`. This means override keys replace defaults with the same key, and new keys are added. + +#### 4.3.10 Rector Sets + +Override sets **replace** ecosystem defaults entirely (not merged). This is because set order matters and partial merging could produce invalid configurations. + +## 5. DevkitConfig + +### 5.1 File Location + +The `devkit.php` file lives at the **project root** (not inside `.kcode/`). This separation ensures: + +- `devkit.php` is committed to git (user-owned configuration). +- `.kcode/` is gitignored (generated, deterministic output). + +Scaffold with `kcode init --config`. + +### 5.2 File Format + +```php + 'value', + // ... +]; +``` + +The file must return an associative array. Non-array returns throw `ConfigurationException::invalidOverride()`. + +### 5.3 Type Safety + +`DevkitConfig::get()` enforces type consistency between the override value and the default: + +```php +$config->get('phpstan_level', 9); // OK: int override for int default +$config->get('phpstan_level', '9'); // Throws: string override for int default +$config->get('source_dirs', null); // OK: null default bypasses type check +``` + +**Rationale for null bypass:** `null` defaults indicate "detect from composer.json if not overridden." The override type is validated implicitly by the consuming code. + +### 5.4 Unknown Keys + +Unknown keys in `devkit.php` are silently ignored. This provides forward-compatibility — a newer devkit version can introduce keys without breaking older config files. + +## 6. ProjectContext + +### 6.1 Invariants + +- All directory paths in `$sourceDirs` and `$testDirs` are absolute. +- `$devkitDir` and `$buildDir` are derived deterministically from `$projectRoot`. +- The object is `final readonly` — no mutation after construction. + +### 6.2 Path Utilities + +```php +$ctx->configPath('phpstan.neon') // → /project/.kcode/phpstan.neon +$ctx->buildPath('coverage') // → /project/.kcode/build/coverage +$ctx->relativeSourceDirs() // → ['src'] +$ctx->relativeTestDirs() // → ['tests'] +$ctx->relativize('/project/src') // → 'src' +``` + +`relativize()` strips the `$projectRoot` prefix. Paths not under the project root are returned unchanged. + +## 7. Ecosystem Defaults + +### 7.1 CS-Fixer Rules + +``` +@PSR12, @PHP84Migration, array_syntax (short), ordered_imports (alpha), +no_unused_imports, trailing_comma_in_multiline, phpdoc_scalar, +unary_operator_spaces, binary_operator_spaces, blank_line_before_statement, +class_attributes_separation, method_argument_space, +single_trait_insert_per_statement, declare_strict_types, +native_function_invocation (@compiler_optimized, namespaced), +not_operator_with_successor_space +``` + +### 7.2 Rector Sets + +``` +LevelSetList::UP_TO_PHP_84, SetList::CODE_QUALITY, SetList::DEAD_CODE, +SetList::EARLY_RETURN, SetList::TYPE_DECLARATION +``` + +### 7.3 Default Exclusions + +- Analysis excludes: `src/Contract` (interfaces are analyzed via implementors) +- Coverage excludes: `src/Exception` (exception classes are trivial) diff --git a/docs/spec/SPEC-002-cli-interface.md b/docs/spec/SPEC-002-cli-interface.md new file mode 100644 index 0000000..7c03a9c --- /dev/null +++ b/docs/spec/SPEC-002-cli-interface.md @@ -0,0 +1,186 @@ +# SPEC-002: CLI Command Interface and Execution Pipeline + +**Version:** 1.0.0 +**Status:** Normative +**Date:** 2025-02-28 +**Author:** Walmir Silva + +## 1. Purpose + +This specification defines the CLI interface, command dispatch, argument parsing, and output formatting for the `kcode` binary. + +## 2. Binary Invocation + +``` +kcode [options] [arguments] +kcode --help | -h | help +kcode --version | -V +``` + +Exit codes follow Unix conventions: `0` = success, `1` = tool failure, `127` = binary not found. + +## 3. Command Registry + +### 3.1 Available Commands + +| Command | Description | Tools Invoked | +|---|---|---| +| `init` | Generate `.kcode/` config directory | None (filesystem only) | +| `migrate` | Detect and remove redundant deps/configs | None (filesystem + composer.json) | +| `test` | Run PHPUnit tests | phpunit | +| `analyse` | Run static analysis | phpstan, psalm | +| `cs:fix` | Fix code style | php-cs-fixer | +| `rector` | Run Rector refactoring | rector | +| `security` | Vulnerability scanning | composer audit | +| `quality` | Full pipeline | cs-fixer, phpstan, psalm, phpunit | +| `format` | Apply formatting | cs-fixer, rector | +| `clean` | Remove build artifacts | None (filesystem only) | + +### 3.2 Command-Specific Options + +#### init + +| Option | Effect | +|---|---| +| `--config` | Scaffold a `devkit.php` override file in the project root | + +#### test + +| Option | Effect | +|---|---| +| `--coverage` | Enable HTML coverage report in `.kcode/build/coverage/` | +| `--suite=Name` | Run only the named test suite | +| All other `--*` flags | Passed through to PHPUnit | + +#### migrate + +| Option | Effect | +|---|---| +| `--dry-run` or `--check` | Show findings without making changes | +| `-n` or `--no-interaction` | Apply all removals without prompting | + +#### cs:fix + +| Option | Effect | +|---|---| +| `--check` or `--dry-run` | Check-only mode (no modifications) | +| All other `--*` flags | Passed through to PHP-CS-Fixer | + +#### rector + +| Option | Effect | +|---|---| +| `--fix` or `--apply` | Apply changes (default is dry-run preview) | +| All other `--*` flags | Passed through to Rector | + +#### quality + +No command-specific options. Runs the pipeline: `cs-fixer --dry-run → phpstan → psalm → phpunit`. Unavailable tools are skipped automatically. + +## 4. Dispatch Architecture + +### 4.1 Application Router + +`Command\Application` maps command names to `AbstractCommand` instances: + +``` +argv → strip script name → match command → execute(Devkit, arguments) → exit code +``` + +Unknown commands produce exit code 1 with a help suggestion. + +### 4.2 Argument Parsing + +`AbstractCommand` provides parsing utilities consumed by subclasses: + +| Method | Purpose | Example | +|---|---|---| +| `hasFlag($args, ...$flags)` | Boolean flag detection | `hasFlag($args, '--coverage')` | +| `option($args, $key, $default)` | Key-value extraction | `option($args, 'suite')` → `'Unit'` | +| `positional($args)` | Non-flag arguments | Filters out all `--*` prefixed args | +| `passthrough($args, $consume)` | Forward remaining args | Strips consumed flags, passes rest | + +### 4.3 Passthrough Pattern + +Commands consume their own flags and forward everything else to the underlying tool: + +```php +// CsFixCommand +$dryRun = $this->hasFlag($arguments, '--check', '--dry-run'); // consume +$passthrough = $this->passthrough($arguments, ['--check', '--dry-run']); // strip consumed +$result = $devkit->run('cs-fixer', [...$extraArgs, ...$passthrough]); // forward rest +``` + +This allows users to pass tool-native flags without the devkit needing to enumerate all possibilities: + +```bash +kcode test --filter=testSpecificMethod --verbose +kcode cs:fix --check --using-cache=no +kcode analyse --level=7 +``` + +## 5. Output Formatting + +### 5.1 Output Streams + +| Stream | Content | +|---|---| +| STDOUT | Info messages, tool output, banners | +| STDERR | Error messages, exception messages | + +### 5.2 ANSI Formatting + +| Method | Prefix | Color | +|---|---|---| +| `info()` | `✓` | Green (32) | +| `warning()` | `⚠` | Yellow (33) | +| `error()` | `✗` | Red (31) | +| `banner()` | Ruler + bold title | Cyan (36) + Bold (1) | +| `line()` | None | Default | + +### 5.3 Banner Format + +``` +──────────────────────────────────────────────────────────── (cyan) + KaririCode Devkit — Command Name (bold) +──────────────────────────────────────────────────────────── (cyan) +``` + +60-character ruler width. Title indented by 2 spaces. + +## 6. Error Handling + +### 6.1 Command-Level + +`Application::run()` wraps each command execution in `try/catch`. Unhandled exceptions produce: + +``` +✗ Exception message here +``` + +Exit code: 1. + +### 6.2 Tool-Level + +When a tool binary is not found, `AbstractToolRunner::run()` returns a `ToolResult` with: + +- `exitCode: 127` +- `stderr: 'Binary not found for "toolName".'` +- `success: false` + +Commands that iterate over multiple tools (analyse, quality, format) skip unavailable tools with a warning and continue. + +## 7. Quality Pipeline Execution Order + +The `quality` command delegates to `Devkit::quality()`, which executes tools in this fixed order: + +``` +1. cs-fixer (--dry-run --diff) +2. phpstan (default args) +3. psalm (default args) +4. phpunit (default args) +``` + +**Rationale for order:** Style issues are cheapest to detect. Static analysis catches type errors before tests run. Tests are the most expensive operation and run last. + +Results are aggregated into a `QualityReport` and the pipeline always completes all available tools (no fail-fast). diff --git a/docs/spec/SPEC-003-tool-runner.md b/docs/spec/SPEC-003-tool-runner.md new file mode 100644 index 0000000..bc979a1 --- /dev/null +++ b/docs/spec/SPEC-003-tool-runner.md @@ -0,0 +1,241 @@ +# SPEC-003: Tool Runner Abstraction and Process Execution + +**Version:** 1.0.0 +**Status:** Normative +**Date:** 2025-02-28 +**Author:** Walmir Silva + +## 1. Purpose + +This specification defines the tool runner contract, process execution model, binary resolution strategy, and result capture semantics. + +## 2. Architecture + +``` +┌──────────────┐ ┌──────────────────┐ ┌────────────────┐ +│ Command │────▸│ Devkit (facade) │────▸│ ToolRunner │ +│ │ │ │ │ (interface) │ +└──────────────┘ └──────────────────┘ └───────┬────────┘ + │ + ┌───────▾────────┐ + │ AbstractTool- │ + │ Runner (base) │ + └───────┬────────┘ + │ delegates + ┌───────▾────────┐ + │ ProcessExecutor │ + │ (proc_open) │ + └───────┬────────┘ + │ returns + ┌───────▾────────┐ + │ ToolResult │ + │ (value object) │ + └────────────────┘ +``` + +## 3. ToolRunner Contract + +```php +interface ToolRunner +{ + public function toolName(): string; + public function isAvailable(): bool; + public function run(array $arguments = []): ToolResult; +} +``` + +### 3.1 Behavioral Requirements + +| Method | Requirement | +|---|---| +| `toolName()` | Returns a stable, unique identifier used as registry key | +| `isAvailable()` | Returns `true` if and only if the binary can be resolved | +| `run()` | Always returns a `ToolResult` — never throws for tool failures | + +### 3.2 Exception Policy + +`run()` must not throw exceptions for tool execution failures. All failure states are captured in `ToolResult`: + +| Failure | ToolResult | +|---|---| +| Binary not found | `exitCode: 127, stderr: 'Binary not found...'` | +| Tool exits non-zero | `exitCode: N, stdout/stderr: tool output` | +| Process spawn failure | `exitCode: 127, stderr: 'Failed to spawn process...'` | + +Exceptions are only thrown for programming errors (e.g., unknown tool name in `Devkit::run()`). + +## 4. AbstractToolRunner + +### 4.1 Template Method Pattern + +Concrete runners implement three abstract methods: + +```php +abstract public function toolName(): string; +abstract protected function vendorBin(): string; +abstract protected function defaultArguments(): array; +``` + +The base class provides `isAvailable()` and `run()`: + +``` +run(arguments) → binary() → [binary, ...defaultArguments(), ...arguments] → executor.execute() +``` + +### 4.2 Binary Caching + +```php +protected function binary(): ?string +{ + return $this->resolvedBinary ??= $this->executor->resolveBinary($this->vendorBin()); +} +``` + +Resolution happens once per process. The null-coalescing assignment operator (`??=`) ensures thread-safe lazy initialization in the single-threaded CLI context. + +### 4.3 Registered Runners + +| Runner | Tool Name | Vendor Binary | Default Args | +|---|---|---|---| +| `PhpUnitRunner` | `phpunit` | `vendor/bin/phpunit` | `--configuration .kcode/phpunit.xml.dist` | +| `PhpStanRunner` | `phpstan` | `vendor/bin/phpstan` | `analyse --configuration ... --no-progress --memory-limit=1G` | +| `CsFixerRunner` | `cs-fixer` | `vendor/bin/php-cs-fixer` | `fix --config ... --diff --ansi` | +| `RectorRunner` | `rector` | `vendor/bin/rector` | `process --config ... --dry-run --ansi` | +| `PsalmRunner` | `psalm` | `vendor/bin/psalm` | `--config ... --no-progress --show-info=false` | +| `ComposerAuditRunner` | `composer-audit` | `vendor/bin/composer` | `audit --format=plain --ansi` | + +### 4.4 ComposerAuditRunner Override + +Composer is typically global. The runner overrides `binary()` to check global PATH before vendor: + +``` +1. Global PATH (command -v composer) +2. Vendor binary (parent::binary()) +``` + +This inverts the standard Tier 2 → Tier 3 order because `vendor/bin/composer` rarely exists. + +## 5. ProcessExecutor + +### 5.1 Process Spawning + +```php +proc_open($command, $descriptors, $pipes, $workingDirectory) +``` + +| Descriptor | Direction | Purpose | +|---|---|---| +| 0 (stdin) | `['pipe', 'r']` | Closed immediately (no interactive input) | +| 1 (stdout) | `['pipe', 'w']` | Captured via `stream_get_contents()` | +| 2 (stderr) | `['pipe', 'w']` | Captured via `stream_get_contents()` | + +### 5.2 Timing + +```php +$start = hrtime(true); +// ... process execution ... +$elapsed = (hrtime(true) - $start) / 1_000_000_000; +``` + +`hrtime(true)` returns nanoseconds as an integer. Division by 10^9 converts to seconds. Result is rounded to 3 decimal places (millisecond precision). + +### 5.3 Binary Resolution (Three-Tier) + +See ADR-004 for full rationale. + +```php +resolveBinary(string $vendorBin): ?string +``` + +| Tier | Check | Example Path | +|---|---|---| +| 1 | `Phar::running(true)/$vendorBin` | `phar:///kcode.phar/vendor/bin/phpunit` | +| 2 | `$workingDirectory/$vendorBin` | `/project/vendor/bin/phpunit` | +| 3 | `command -v $basename` | `/usr/local/bin/phpunit` | + +Tier 1 is only checked when running inside a PHAR (`Phar::running(false) !== ''`). + +### 5.4 Security + +- `escapeshellarg()` is used for all shell-injected values in `command -v` calls. +- `proc_open()` accepts command as an array (no shell interpolation). + +## 6. ToolResult + +### 6.1 Structure + +```php +final readonly class ToolResult +{ + public bool $success; + + public function __construct( + public string $toolName, + public int $exitCode, + public string $stdout, + public string $stderr, + public float $elapsedSeconds, + ); +} +``` + +### 6.2 Derived Property + +`$success = (0 === $exitCode)` — computed in constructor, immutable. + +### 6.3 Combined Output + +```php +public function output(): string +``` + +Returns `trim(stdout + "\n" + stderr)`. Falls back to `'(no output)'` when both streams are empty. + +## 7. QualityReport + +### 7.1 Aggregation + +```php +final readonly class QualityReport +{ + public bool $passed; // all results successful + public float $totalSeconds; // sum of elapsed times + public int $failureCount; // count of failed results + + public function __construct(public array $results); + public function failures(): array; // filtered failed results +} +``` + +### 7.2 Pipeline Semantics + +The quality pipeline always completes all tools. `$passed` reflects the aggregate. Individual tool results are accessible via `$results` for detailed reporting. + +## 8. Argument Flow + +Complete argument flow from CLI to tool binary: + +``` +User CLI input: + kcode test --coverage --suite=Unit --verbose + +↓ Application strips "test" + +Command receives: + ['--coverage', '--suite=Unit', '--verbose'] + +↓ TestCommand processes + + extraArgs: ['--coverage-html', '.kcode/build/coverage', '--testsuite', 'Unit'] + passthrough: ['--verbose'] (--coverage and --suite=Unit consumed) + +↓ Devkit::run('phpunit', allArgs) + +Runner prepends defaults: + ['vendor/bin/phpunit', '--configuration', '.kcode/phpunit.xml.dist', + '--coverage-html', '.kcode/build/coverage', '--testsuite', 'Unit', '--verbose'] + +↓ ProcessExecutor::execute() + +proc_open() receives full command array +``` diff --git a/infection.json b/infection.json deleted file mode 100644 index cad07f6..0000000 --- a/infection.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "$schema": "vendor/infection/infection/resources/schema.json", - "source": { - "directories": [ - "src" - ] - }, - "timeout": 30, - "logs": { - "text": "infection.log", - "html": "infection.html", - "summary": "infection-summary.log", - "debug": "infection-debug.log", - "perMutator": "infection-per-mutator.md" - }, - "tmpDir": "var/cache/infection", - "phpUnit": { - "configDir": ".", - "customPath": "vendor/bin/phpunit" - }, - "minMsi": 80, - "minCoveredMsi": 90, - "mutators": { - "@default": true, - "@function_signature": false, - "MethodCallRemoval": { - "ignore": [] - } - }, - "testFramework": "phpunit", - "bootstrap": "vendor/autoload.php" -} diff --git a/phpbench.json b/phpbench.json deleted file mode 100644 index aae4e40..0000000 --- a/phpbench.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "$schema": "./vendor/phpbench/phpbench/phpbench.schema.json", - "runner.bootstrap": "vendor/autoload.php", - "runner.path": "benchmarks", - "runner.php_config": { - "memory_limit": "1G" - }, - "runner.executors": { - "profiling": { - "executor": "local", - "php_config": { - "xdebug.mode": "profile", - "xdebug.output_dir": "build/profile" - } - } - }, - "report.generators": { - "table": { - "cols": [ - "benchmark", - "subject", - "revs", - "its", - "mem_peak", - "best", - "mean", - "mode", - "worst", - "stdev", - "rstdev", - "diff" - ] - } - }, - "runner.iterations": 10, - "runner.revs": 1000 -} diff --git a/phpcs.xml b/phpcs.xml deleted file mode 100644 index c423cc0..0000000 --- a/phpcs.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - Coding standard with exceptions for PHP keyword conflicts - - - src - tests - - - vendor/* - var/* - build/* - coverage/* - public/index.php - *.blade.php - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/phpstan.neon b/phpstan.neon deleted file mode 100644 index 6658f4c..0000000 --- a/phpstan.neon +++ /dev/null @@ -1,32 +0,0 @@ -parameters: - level: max - paths: - - src - excludePaths: - - tests/Fixtures (?) - tmpDir: var/cache/phpstan - - # Allow analysis even with no files initially - reportUnmatchedIgnoredErrors: false - treatPhpDocTypesAsCertain: false - - # Case sensitivity checks - checkClassCaseSensitivity: true - checkFunctionNameCase: true - checkInternalClassCaseSensitivity: true - - # Strict production quality rules - checkExplicitMixedMissingReturn: true - checkUninitializedProperties: true - checkTooWideReturnTypesInProtectedAndPublicMethods: true - - # Scope pollution prevention - polluteScopeWithLoopInitialAssignments: false - polluteScopeWithAlwaysIterableForeach: false - - # PHPDoc flexibility - reportMaybesInMethodSignatures: false - reportStaticMethodSignatures: false - -includes: - - vendor/phpstan/phpstan-strict-rules/rules.neon diff --git a/phpunit.xml b/phpunit.xml index fd3e50d..8afc8f6 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,11 +1,17 @@ + displayDetailsOnTestsThatTriggerErrors="true" + displayDetailsOnTestsThatTriggerNotices="true" + displayDetailsOnTestsThatTriggerWarnings="true" +> tests/Unit @@ -13,9 +19,6 @@ tests/Integration - - tests/Functional - @@ -32,14 +35,12 @@ - - - + diff --git a/src/Command/AbstractCommand.php b/src/Command/AbstractCommand.php new file mode 100644 index 0000000..e4731f6 --- /dev/null +++ b/src/Command/AbstractCommand.php @@ -0,0 +1,139 @@ + $arguments Raw CLI arguments after command name. */ + abstract public function execute(Devkit $devkit, array $arguments): int; + + // ── Output Helpers ──────────────────────────────────────────── + + protected function info(string $message): void + { + fwrite(\STDOUT, "\033[32m✓\033[0m {$message}" . \PHP_EOL); + } + + protected function warning(string $message): void + { + fwrite(\STDOUT, "\033[33m⚠\033[0m {$message}" . \PHP_EOL); + } + + protected function error(string $message): void + { + fwrite(\STDERR, "\033[31m✗\033[0m {$message}" . \PHP_EOL); + } + + protected function line(string $message = ''): void + { + fwrite(\STDOUT, $message . \PHP_EOL); + } + + protected function banner(string $title): void + { + $ruler = str_repeat('─', 60); + $this->line("\033[36m{$ruler}\033[0m"); + $this->line("\033[1m {$title}\033[0m"); + $this->line("\033[36m{$ruler}\033[0m"); + } + + protected function section(string $title): void + { + $this->line(); + $this->line("\033[33m {$title}\033[0m"); + $this->line(); + } + + /** + * Interactive yes/no confirmation via STDIN. + * + * @param bool $default Default answer when user presses Enter without input. + */ + protected function confirm(string $question, bool $default = false): bool + { + $hint = $default ? '[Y/n]' : '[y/N]'; + fwrite(\STDOUT, "\033[33m?\033[0m {$question} {$hint} "); + + $input = trim((string) fgets(\STDIN)); + + if ('' === $input) { + return $default; + } + + return \in_array(strtolower($input), ['y', 'yes', 'sim', 's'], true); + } + + // ── Argument Helpers ────────────────────────────────────────── + + /** @param list $arguments */ + protected function hasFlag(array $arguments, string ...$flags): bool + { + foreach ($flags as $flag) { + if (\in_array($flag, $arguments, true)) { + return true; + } + } + + return false; + } + + /** + * Extract --key=value from arguments. + * + * @param list $arguments + */ + protected function option(array $arguments, string $key, ?string $default = null): ?string + { + $prefix = "--{$key}="; + + foreach ($arguments as $arg) { + if (str_starts_with($arg, $prefix)) { + return substr($arg, \strlen($prefix)); + } + } + + return $default; + } + + /** + * Return arguments that are not flags (--xxx). + * + * @param list $arguments + * @return list + */ + protected function positional(array $arguments): array + { + return array_values(array_filter( + $arguments, + static fn (string $arg): bool => ! str_starts_with($arg, '--'), + )); + } + + /** + * Filter arguments to pass through to underlying tool. + * + * @param list $arguments + * @param list $consume Flags consumed by the command itself. + * @return list + */ + protected function passthrough(array $arguments, array $consume = []): array + { + return array_values(array_filter( + $arguments, + static fn (string $arg): bool => ! \in_array($arg, $consume, true), + )); + } +} diff --git a/src/Command/AnalyseCommand.php b/src/Command/AnalyseCommand.php new file mode 100644 index 0000000..56f4407 --- /dev/null +++ b/src/Command/AnalyseCommand.php @@ -0,0 +1,58 @@ +banner('KaririCode Devkit — Analyse'); + + $exitCode = 0; + + foreach (['phpstan', 'psalm'] as $tool) { + if (! $devkit->isToolAvailable($tool)) { + $this->warning("{$tool} not available — skipping"); + + continue; + } + + $this->line("\033[1m▸ Running {$tool}…\033[0m"); + $result = $devkit->run($tool, $arguments); + $this->line($result->output()); + + if ($result->success) { + $this->info(\sprintf('%s passed (%.2fs)', $tool, $result->elapsedSeconds)); + } else { + $this->error(\sprintf('%s failed — exit code %d (%.2fs)', $tool, $result->exitCode, $result->elapsedSeconds)); + $exitCode = max($exitCode, $result->exitCode); + } + + $this->line(); + } + + return $exitCode; + } +} diff --git a/src/Command/Application.php b/src/Command/Application.php new file mode 100644 index 0000000..37f78d2 --- /dev/null +++ b/src/Command/Application.php @@ -0,0 +1,118 @@ + */ + private array $commands = []; + + public function __construct( + private readonly Devkit $devkit, + ) { + } + + public function register(AbstractCommand $command): void + { + $this->commands[$command->name()] = $command; + } + + /** @param list $argv Raw $argv from CLI. */ + public function run(array $argv): int + { + // Strip script name + array_shift($argv); + + if ([] === $argv || $this->isHelp($argv)) { + $this->printUsage(); + + return 0; + } + + if ($this->isVersion($argv)) { + $this->printVersion(); + + return 0; + } + + $commandName = array_shift($argv); + $command = $this->commands[$commandName] ?? null; + + if (null === $command) { + fwrite(\STDERR, "\033[31m✗\033[0m Unknown command: {$commandName}" . \PHP_EOL); + fwrite(\STDERR, " Run \033[1mkcode --help\033[0m for available commands." . \PHP_EOL); + + return 1; + } + + try { + return $command->execute($this->devkit, $argv); + } catch (\Throwable $exception) { + fwrite(\STDERR, "\033[31m✗\033[0m {$exception->getMessage()}" . \PHP_EOL); + + return 1; + } + } + + // ── Internals ───────────────────────────────────────────────── + + /** @param list $argv */ + private function isHelp(array $argv): bool + { + return \in_array($argv[0] ?? '', ['--help', '-h', 'help'], true); + } + + /** @param list $argv */ + private function isVersion(array $argv): bool + { + return \in_array($argv[0] ?? '', ['--version', '-V'], true); + } + + private function printVersion(): void + { + fwrite(\STDOUT, \sprintf( + "\033[1mKaririCode Devkit\033[0m %s" . \PHP_EOL, + Devkit::version(), + )); + } + + private function printUsage(): void + { + $this->printVersion(); + fwrite(\STDOUT, \PHP_EOL); + fwrite(\STDOUT, "\033[33mUsage:\033[0m" . \PHP_EOL); + fwrite(\STDOUT, " kcode [options] [arguments]" . \PHP_EOL . \PHP_EOL); + fwrite(\STDOUT, "\033[33mAvailable commands:\033[0m" . \PHP_EOL); + + $maxLen = 0; + + foreach ($this->commands as $name => $command) { + $maxLen = max($maxLen, \strlen($name)); + } + + foreach ($this->commands as $name => $command) { + fwrite(\STDOUT, \sprintf( + " \033[32m%-{$maxLen}s\033[0m %s" . \PHP_EOL, + $name, + $command->description(), + )); + } + + fwrite(\STDOUT, \PHP_EOL); + fwrite(\STDOUT, "\033[33mOptions:\033[0m" . \PHP_EOL); + fwrite(\STDOUT, " \033[32m-h, --help\033[0m Show this help" . \PHP_EOL); + fwrite(\STDOUT, " \033[32m-V, --version\033[0m Show version" . \PHP_EOL); + } +} diff --git a/src/Command/CleanCommand.php b/src/Command/CleanCommand.php new file mode 100644 index 0000000..8708b31 --- /dev/null +++ b/src/Command/CleanCommand.php @@ -0,0 +1,39 @@ +banner('KaririCode Devkit — Clean'); + + $devkit->clean(); + + $this->info('Build directory cleaned: .kcode/build/'); + + return 0; + } +} diff --git a/src/Command/CsFixCommand.php b/src/Command/CsFixCommand.php new file mode 100644 index 0000000..a38da08 --- /dev/null +++ b/src/Command/CsFixCommand.php @@ -0,0 +1,52 @@ +hasFlag($arguments, '--check', '--dry-run'); + $mode = $dryRun ? 'checking' : 'fixing'; + + $this->banner("KaririCode Devkit — CS {$mode}"); + + $extraArgs = $dryRun ? ['--dry-run'] : []; + $passthrough = $this->passthrough($arguments, ['--check', '--dry-run']); + + $result = $devkit->run('cs-fixer', [...$extraArgs, ...$passthrough]); + + $this->line($result->output()); + $this->line(); + + if ($result->success) { + $this->info(\sprintf('Code style %s (%.2fs)', $dryRun ? 'OK' : 'fixed', $result->elapsedSeconds)); + } else { + $this->error(\sprintf('Code style issues found (%.2fs)', $result->elapsedSeconds)); + } + + return $result->exitCode; + } +} diff --git a/src/Command/FormatCommand.php b/src/Command/FormatCommand.php new file mode 100644 index 0000000..9058266 --- /dev/null +++ b/src/Command/FormatCommand.php @@ -0,0 +1,67 @@ +banner('KaririCode Devkit — Format'); + + $exitCode = 0; + + // Step 1: CS-Fixer fix + if ($devkit->isToolAvailable('cs-fixer')) { + $this->line("\033[1m▸ Running php-cs-fixer fix…\033[0m"); + $result = $devkit->run('cs-fixer', $arguments); + $this->line($result->output()); + + if ($result->success) { + $this->info(\sprintf('CS-Fixer done (%.2fs)', $result->elapsedSeconds)); + } else { + $this->error(\sprintf('CS-Fixer failed (%.2fs)', $result->elapsedSeconds)); + $exitCode = $result->exitCode; + } + + $this->line(); + } + + // Step 2: Rector apply (--no-dry-run overrides runner default) + if ($devkit->isToolAvailable('rector')) { + $this->line("\033[1m▸ Running rector process…\033[0m"); + $result = $devkit->run('rector', ['--no-dry-run', ...$arguments]); + $this->line($result->output()); + + if ($result->success) { + $this->info(\sprintf('Rector done (%.2fs)', $result->elapsedSeconds)); + } else { + $this->error(\sprintf('Rector failed (%.2fs)', $result->elapsedSeconds)); + $exitCode = max($exitCode, $result->exitCode); + } + } + + return $exitCode; + } +} diff --git a/src/Command/InitCommand.php b/src/Command/InitCommand.php new file mode 100644 index 0000000..645c60a --- /dev/null +++ b/src/Command/InitCommand.php @@ -0,0 +1,157 @@ +banner('KaririCode Devkit — Init'); + + $context = $devkit->context(); + $this->info("Project: {$context->projectName}"); + $this->info("Namespace: {$context->namespace}"); + $this->info("PHP: {$context->phpVersion}"); + + $count = $devkit->init(); + + $this->line(); + $this->info("Generated {$count} config file(s) in .kcode/"); + $this->info(".kcode/ added to .gitignore (regenerate with kcode init)"); + + // Scaffold devkit.php if requested + if ($this->hasFlag($arguments, '--config')) { + $this->scaffoldDevkitConfig($context->projectRoot, $context); + } + + // Hint: detect redundant root-level configs and dev dependencies + $detector = new \KaririCode\Devkit\Core\MigrationDetector(); + $migration = $detector->detect($context->projectRoot); + + if ($migration->hasRedundancies) { + $this->line(); + $this->warning(\sprintf( + 'Found %d redundant item(s) that kcode replaces.', + $migration->totalItems, + )); + $this->line(' Run \033[1mkcode migrate\033[0m to review and clean up.'); + } + + return 0; + } + + private function scaffoldDevkitConfig(string $projectRoot, \KaririCode\Devkit\Core\ProjectContext $context): void + { + $configPath = $projectRoot . \DIRECTORY_SEPARATOR . 'devkit.php'; + + if (is_file($configPath)) { + $this->warning('devkit.php already exists — skipping scaffold.'); + + return; + } + + $content = <<<'PHP' + 'kariricode/my-component', + // 'namespace' => 'KaririCode\\MyComponent', + + // ── PHP Version ─────────────────────────────────────────── + // 'php_version' => '8.4', + + // ── Static Analysis ─────────────────────────────────────── + // 'phpstan_level' => 9, // 0–9 (default: 9) + // 'psalm_level' => 3, // 1–9 (default: 3) + + // ── Directories ─────────────────────────────────────────── + // 'source_dirs' => ['src'], + // 'test_dirs' => ['tests'], + // 'exclude_dirs' => ['src/Contract'], // excluded from static analysis + + // ── Test Suites ─────────────────────────────────────────── + // 'test_suites' => [ + // 'Unit' => 'tests/Unit', + // 'Integration' => 'tests/Integration', + // ], + + // ── Coverage ────────────────────────────────────────────── + // 'coverage_exclude' => ['src/Exception'], + + // ── Code Style (MERGED with KaririCode defaults) ────────── + // 'cs_fixer_rules' => [ + // 'concat_space' => ['spacing' => 'one'], + // 'yoda_style' => false, + // ], + + // ── Rector (REPLACES KaririCode defaults) ───────────────── + // 'rector_sets' => [ + // 'LevelSetList::UP_TO_PHP_84', + // 'SetList::CODE_QUALITY', + // 'SetList::DEAD_CODE', + // 'SetList::EARLY_RETURN', + // 'SetList::TYPE_DECLARATION', + // ], + + // ── Tool Versions (informational) ───────────────────────── + // 'tools' => [ + // 'phpunit' => '^11.0', + // 'phpstan' => '^2.0', + // 'php-cs-fixer' => '^3.64', + // 'rector' => '^2.0', + // 'psalm' => '^6.0', + // ], + ]; + PHP; + + file_put_contents($configPath, $content . \PHP_EOL); + + $this->line(); + $this->info('Scaffolded devkit.php in project root.'); + $this->line(' Edit it, then run \033[1mkcode init\033[0m to regenerate configs.'); + } +} diff --git a/src/Command/MigrateCommand.php b/src/Command/MigrateCommand.php new file mode 100644 index 0000000..5a186af --- /dev/null +++ b/src/Command/MigrateCommand.php @@ -0,0 +1,162 @@ +banner('KaririCode Devkit — Migrate'); + + $dryRun = $this->hasFlag($arguments, '--dry-run', '--check'); + $noInteraction = $this->hasFlag($arguments, '--no-interaction', '-n'); + + $context = $devkit->context(); + $report = $this->detector->detect($context->projectRoot); + + if (! $report->hasRedundancies) { + $this->info('No redundant dependencies or config files found. Project is clean.'); + + return 0; + } + + $this->renderReport($report); + + if ($dryRun) { + $this->warning('Dry-run mode — no changes applied.'); + + return 0; + } + + // ── Config files & caches ───────────────────────────────── + $filesRemoved = 0; + + if ($report->hasConfigFiles() || $report->hasCachePaths()) { + $shouldRemoveFiles = $noInteraction || $this->confirm( + 'Remove these config files and cache paths?', + ); + + if ($shouldRemoveFiles) { + $filesRemoved = $report->removeFiles(); + $this->info("Removed {$filesRemoved} file(s)/directory(ies)."); + } else { + $this->warning('Skipped file removal.'); + } + } + + // ── Composer.json require-dev ───────────────────────────── + $packagesRemoved = []; + + if ($report->hasPackages()) { + $shouldRemovePackages = $noInteraction || $this->confirm( + 'Remove these packages from composer.json require-dev?', + ); + + if ($shouldRemovePackages) { + $packagesRemoved = $report->removePackagesFromComposer(); + + if ([] !== $packagesRemoved) { + $this->info(\sprintf( + 'Removed %d package(s) from composer.json: %s', + \count($packagesRemoved), + implode(', ', $packagesRemoved), + )); + } + } else { + $this->warning('Skipped composer.json modification.'); + } + } + + // ── Summary ────────────────────────────────────────────── + $this->section('Summary'); + + $totalActioned = $filesRemoved + \count($packagesRemoved); + + if ($totalActioned > 0) { + $this->info("{$totalActioned} item(s) cleaned up."); + + if ([] !== $packagesRemoved) { + $this->line(); + $this->warning('Run \033[1mcomposer update\033[0m to apply dependency changes.'); + } + } else { + $this->warning('No changes applied.'); + } + + return 0; + } + + private function renderReport(MigrationReport $report): void + { + $this->line(\sprintf( + ' Found \033[1m%d\033[0m redundant item(s) that kcode replaces:', + $report->totalItems, + )); + + if ($report->hasPackages()) { + $this->section('composer.json require-dev'); + + foreach ($report->redundantPackages as $package => $version) { + $this->line(" \033[31m✗\033[0m {$package}: {$version}"); + } + } + + if ($report->hasConfigFiles()) { + $this->section('Root-level config files'); + + foreach ($report->redundantConfigFiles as $file) { + $this->line(" \033[31m✗\033[0m {$file}"); + } + } + + if ($report->hasCachePaths()) { + $this->section('Root-level cache paths'); + + foreach ($report->redundantCachePaths as $cachePath) { + $isDir = is_dir($report->projectRoot . \DIRECTORY_SEPARATOR . $cachePath); + $suffix = $isDir ? '/' : ''; + $this->line(" \033[31m✗\033[0m {$cachePath}{$suffix}"); + } + } + + $this->line(); + $this->line(' These are replaced by \033[1m.kcode/\033[0m generated configs.'); + $this->line(); + } +} diff --git a/src/Command/QualityCommand.php b/src/Command/QualityCommand.php new file mode 100644 index 0000000..dc65579 --- /dev/null +++ b/src/Command/QualityCommand.php @@ -0,0 +1,87 @@ +banner('KaririCode Devkit — Quality Pipeline'); + + $report = $devkit->quality(); + + foreach ($report->results as $result) { + $this->renderToolResult($result); + } + + // Summary + $this->line(); + + if ($report->passed) { + $this->info(\sprintf( + 'All %d tool(s) passed (%.2fs total)', + \count($report->results), + $report->totalSeconds, + )); + + return 0; + } + + $this->error(\sprintf( + '%d of %d tool(s) failed (%.2fs total)', + $report->failureCount, + \count($report->results), + $report->totalSeconds, + )); + + foreach ($report->failures() as $failure) { + $this->error(" └─ {$failure->toolName} (exit {$failure->exitCode})"); + } + + return 1; + } + + private function renderToolResult(ToolResult $result): void + { + if ($result->success) { + $this->info(\sprintf( + '%s passed (%.2fs)', + $result->toolName, + $result->elapsedSeconds, + )); + } else { + $this->error(\sprintf( + '%s failed — exit code %d (%.2fs)', + $result->toolName, + $result->exitCode, + $result->elapsedSeconds, + )); + $this->line($result->output()); + } + + $this->line(); + } +} diff --git a/src/Command/RectorCommand.php b/src/Command/RectorCommand.php new file mode 100644 index 0000000..3fd09a4 --- /dev/null +++ b/src/Command/RectorCommand.php @@ -0,0 +1,55 @@ +hasFlag($arguments, '--fix', '--apply'); + $mode = $apply ? 'applying' : 'previewing'; + + $this->banner("KaririCode Devkit — Rector ({$mode})"); + + $passthrough = $this->passthrough($arguments, ['--fix', '--apply']); + + // RectorRunner defaults to --dry-run for safety. + // When applying, --no-dry-run overrides it (Rector: last flag wins). + $result = $apply + ? $devkit->run('rector', ['--no-dry-run', ...$passthrough]) + : $devkit->run('rector', $passthrough); + + $this->line($result->output()); + $this->line(); + + if ($result->success) { + $this->info(\sprintf('Rector %s (%.2fs)', $apply ? 'applied' : 'clean', $result->elapsedSeconds)); + } else { + $this->error(\sprintf('Rector found issues (%.2fs)', $result->elapsedSeconds)); + } + + return $result->exitCode; + } +} diff --git a/src/Command/SecurityCommand.php b/src/Command/SecurityCommand.php new file mode 100644 index 0000000..d643edc --- /dev/null +++ b/src/Command/SecurityCommand.php @@ -0,0 +1,46 @@ +banner('KaririCode Devkit — Security Audit'); + + $result = $devkit->run('composer-audit', $arguments); + + $this->line($result->output()); + $this->line(); + + if ($result->success) { + $this->info(\sprintf('No known vulnerabilities (%.2fs)', $result->elapsedSeconds)); + } else { + $this->error(\sprintf('Vulnerabilities found (%.2fs)', $result->elapsedSeconds)); + } + + return $result->exitCode; + } +} diff --git a/src/Command/TestCommand.php b/src/Command/TestCommand.php new file mode 100644 index 0000000..54e3185 --- /dev/null +++ b/src/Command/TestCommand.php @@ -0,0 +1,71 @@ +banner('KaririCode Devkit — Test'); + + $extraArgs = []; + + if ($this->hasFlag($arguments, '--coverage')) { + $extraArgs[] = '--coverage-html'; + $extraArgs[] = $devkit->context()->buildPath('coverage'); + } + + $suite = $this->option($arguments, 'suite'); + if (null !== $suite) { + $extraArgs[] = '--testsuite'; + $extraArgs[] = $suite; + } + + $passthrough = $this->passthrough($arguments, ['--coverage']); + + // Strip consumed --suite=X option (prefix match, not exact) + $passthrough = array_values(array_filter( + $passthrough, + static fn (string $arg): bool => ! str_starts_with($arg, '--suite='), + )); + + $allArgs = [...$extraArgs, ...$passthrough]; + + $result = $devkit->run('phpunit', $allArgs); + + $this->line($result->output()); + $this->line(); + + if ($result->success) { + $this->info(\sprintf('Tests passed (%.2fs)', $result->elapsedSeconds)); + } else { + $this->error(\sprintf('Tests failed — exit code %d (%.2fs)', $result->exitCode, $result->elapsedSeconds)); + } + + return $result->exitCode; + } +} diff --git a/src/Configuration/CsFixerConfigGenerator.php b/src/Configuration/CsFixerConfigGenerator.php new file mode 100644 index 0000000..5680d36 --- /dev/null +++ b/src/Configuration/CsFixerConfigGenerator.php @@ -0,0 +1,85 @@ +relativeSourceDirs() as $dir) { + $finderDirs .= " ->in(__DIR__ . '/../{$dir}')\n"; + } + + foreach ($context->relativeTestDirs() as $dir) { + $finderDirs .= " ->in(__DIR__ . '/../{$dir}')\n"; + } + + $rulesExport = $this->exportRules($context->csFixerRules); + + return <<name('*.php') + ->ignoreDotFiles(true) + ->ignoreVCS(true); + + return (new PhpCsFixer\\Config()) + ->setRules({$rulesExport}) + ->setFinder(\$finder) + ->setRiskyAllowed(true) + ->setUsingCache(true) + ->setCacheFile(__DIR__ . '/build/.php-cs-fixer.cache'); + + PHP; + } + + /** @param array $rules */ + private function exportRules(array $rules): string + { + $export = var_export($rules, true); + + // Normalize var_export output to short array syntax + $export = (string) preg_replace('/^array \($/m', '[', $export); + $export = (string) preg_replace('/^\)$/m', ']', $export); + $export = str_replace('array (', '[', $export); + $export = str_replace(')', ']', $export); + + return $export; + } +} diff --git a/src/Configuration/PhpStanConfigGenerator.php b/src/Configuration/PhpStanConfigGenerator.php new file mode 100644 index 0000000..9d90d34 --- /dev/null +++ b/src/Configuration/PhpStanConfigGenerator.php @@ -0,0 +1,64 @@ + " - ../{$d}", + $context->relativeSourceDirs(), + )); + + $excludes = ''; + if ([] !== $context->excludeDirs) { + $items = implode("\n", array_map( + static fn (string $d): string => " - ../{$d}", + $context->excludeDirs, + )); + $excludes = <<phpstanLevel} + paths: + {$paths} + {$excludes} + tmpDir: build/.phpstan + checkMissingCallableSignature: true + treatPhpDocTypesAsCertain: false + reportUnmatchedIgnoredErrors: true + + NEON; + } +} diff --git a/src/Configuration/PhpUnitConfigGenerator.php b/src/Configuration/PhpUnitConfigGenerator.php new file mode 100644 index 0000000..c800f0a --- /dev/null +++ b/src/Configuration/PhpUnitConfigGenerator.php @@ -0,0 +1,116 @@ +renderSuites($context); + $sourceIncludes = $this->renderDirList($context->relativeSourceDirs(), 12); + $coverageExcludes = $this->renderDirList($context->coverageExclude, 12); + + return << + + + + + + + + + + + + {$suites} + + + + {$sourceIncludes} + + {$coverageExcludes} + + + + + + + + + + + + + + + + XML; + } + + private function renderSuites(ProjectContext $context): string + { + $xml = ''; + + foreach ($context->testSuites as $name => $relativeDir) { + $xml .= " \n"; + $xml .= " ../{$relativeDir}\n"; + $xml .= " \n"; + } + + return $xml; + } + + /** @param list $dirs */ + private function renderDirList(array $dirs, int $indent): string + { + $pad = str_repeat(' ', $indent); + $xml = ''; + + foreach ($dirs as $dir) { + $xml .= "{$pad}../{$dir}\n"; + } + + return $xml; + } +} diff --git a/src/Configuration/PsalmConfigGenerator.php b/src/Configuration/PsalmConfigGenerator.php new file mode 100644 index 0000000..d01dd97 --- /dev/null +++ b/src/Configuration/PsalmConfigGenerator.php @@ -0,0 +1,60 @@ +relativeSourceDirs() as $dir) { + $sourceDirs .= " \n"; + } + + return << + + + + {$sourceDirs} + + + + + + XML; + } +} diff --git a/src/Configuration/RectorConfigGenerator.php b/src/Configuration/RectorConfigGenerator.php new file mode 100644 index 0000000..dac7774 --- /dev/null +++ b/src/Configuration/RectorConfigGenerator.php @@ -0,0 +1,83 @@ +relativeSourceDirs() as $dir) { + $paths .= " __DIR__ . '/../{$dir}',\n"; + } + + foreach ($context->relativeTestDirs() as $dir) { + $paths .= " __DIR__ . '/../{$dir}',\n"; + } + + $sets = $this->renderSets($context->rectorSets); + + return <<withPaths([ + {$paths} ]) + ->withPhpSets(php84: true) + ->withSets([ + {$sets} ]) + ->withImportNames( + importShortClasses: false, + removeUnusedImports: true, + ); + + PHP; + } + + /** @param list $sets */ + private function renderSets(array $sets): string + { + $lines = ''; + + foreach ($sets as $set) { + $lines .= " {$set},\n"; + } + + return $lines; + } +} diff --git a/src/Contract/ConfigGenerator.php b/src/Contract/ConfigGenerator.php new file mode 100644 index 0000000..61b4d4f --- /dev/null +++ b/src/Contract/ConfigGenerator.php @@ -0,0 +1,29 @@ + $arguments Extra CLI args forwarded to the tool. */ + public function run(array $arguments = []): ToolResult; +} diff --git a/src/Core/Devkit.php b/src/Core/Devkit.php new file mode 100644 index 0000000..b994bfc --- /dev/null +++ b/src/Core/Devkit.php @@ -0,0 +1,238 @@ + */ + private array $generators = []; + + /** @var array */ + private array $runners = []; + + public function __construct( + private readonly ProjectDetector $detector, + ) { + } + + public static function version(): string + { + return self::VERSION; + } + + // ── Registration ────────────────────────────────────────────── + + public function addGenerator(ConfigGenerator $generator): void + { + $this->generators[$generator->toolName()] = $generator; + } + + public function addRunner(ToolRunner $runner): void + { + $this->runners[$runner->toolName()] = $runner; + } + + // ── Context ─────────────────────────────────────────────────── + + public function context(string $workingDirectory = '.'): ProjectContext + { + return $this->context ??= $this->detector->detect( + realpath($workingDirectory) ?: $workingDirectory, + ); + } + + // ── Init ────────────────────────────────────────────────────── + + /** Generate all config files inside `.kcode/`. Returns file count. */ + public function init(string $workingDirectory = '.'): int + { + $ctx = $this->context($workingDirectory); + $this->ensureDirectories($ctx); + + $count = 0; + + foreach ($this->generators as $generator) { + $path = $ctx->configPath($generator->outputPath()); + + $dir = \dirname($path); + if (! is_dir($dir)) { + mkdir($dir, 0o755, true); + } + + file_put_contents($path, $generator->generate($ctx)); + ++$count; + } + + $this->appendGitIgnore($ctx); + + return $count; + } + + // ── Run ─────────────────────────────────────────────────────── + + /** @param list $arguments */ + public function run(string $toolName, array $arguments = []): ToolResult + { + $runner = $this->runners[$toolName] ?? null; + + if (null === $runner) { + throw new DevkitException(\sprintf( + 'Unknown tool "%s". Available: %s', + $toolName, + implode(', ', array_keys($this->runners)), + )); + } + + return $runner->run($arguments); + } + + /** Check if a tool runner is registered and its binary is available. */ + public function isToolAvailable(string $toolName): bool + { + return isset($this->runners[$toolName]) && $this->runners[$toolName]->isAvailable(); + } + + // ── Quality Pipeline ────────────────────────────────────────── + + /** + * Full quality pipeline: cs-check → analyse → test. + * + * Skips unavailable tools instead of failing. + * + * @param list $onlyTools Restrict to these tools (empty = all). + */ + public function quality(array $onlyTools = []): QualityReport + { + $pipeline = [] !== $onlyTools + ? $onlyTools + : ['cs-fixer', 'phpstan', 'psalm', 'phpunit']; + + $results = []; + + foreach ($pipeline as $tool) { + if (! $this->isToolAvailable($tool)) { + continue; + } + + $extraArgs = match ($tool) { + 'cs-fixer' => ['--dry-run', '--diff'], + 'rector' => ['--dry-run'], + default => [], + }; + + $results[] = $this->run($tool, $extraArgs); + } + + return new QualityReport($results); + } + + // ── Clean ───────────────────────────────────────────────────── + + public function clean(string $workingDirectory = '.'): void + { + $buildDir = $this->context($workingDirectory)->buildDir; + + if (is_dir($buildDir)) { + $this->removeRecursive($buildDir); + } + + mkdir($buildDir, 0o755, true); + } + + /** @return list */ + public function registeredTools(): array + { + return array_keys($this->runners); + } + + // ── Internals ───────────────────────────────────────────────── + + private function ensureDirectories(ProjectContext $ctx): void + { + foreach ([$ctx->devkitDir, $ctx->buildDir] as $dir) { + if (! is_dir($dir) && ! mkdir($dir, 0o755, true)) { + throw DevkitException::directoryNotWritable($dir); + } + } + } + + private function appendGitIgnore(ProjectContext $ctx): void + { + $gitignore = $ctx->projectRoot . \DIRECTORY_SEPARATOR . '.gitignore'; + $entry = '.kcode/'; + + // Create .gitignore if it doesn't exist + if (! is_file($gitignore)) { + file_put_contents( + $gitignore, + '# KaririCode Devkit — generated configs and build artifacts' . \PHP_EOL + . $entry . \PHP_EOL, + ); + + return; + } + + $content = file_get_contents($gitignore); + + if (false === $content) { + return; + } + + // Already covered + if (str_contains($content, $entry)) { + return; + } + + // Migrate: if old .kcode/build/ entry exists, replace with .kcode/ + $legacyEntry = '.kcode/build/'; + if (str_contains($content, $legacyEntry)) { + $content = str_replace($legacyEntry, $entry, $content); + file_put_contents($gitignore, $content); + + return; + } + + file_put_contents( + $gitignore, + \PHP_EOL . '# KaririCode Devkit — generated configs and build artifacts' . \PHP_EOL + . $entry . \PHP_EOL, + \FILE_APPEND, + ); + } + + private function removeRecursive(string $dir): void + { + $items = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS), + \RecursiveIteratorIterator::CHILD_FIRST, + ); + + foreach ($items as $item) { + /** @var \SplFileInfo $item */ + $item->isDir() ? rmdir($item->getPathname()) : unlink($item->getPathname()); + } + + rmdir($dir); + } +} diff --git a/src/Core/DevkitConfig.php b/src/Core/DevkitConfig.php new file mode 100644 index 0000000..9cc9204 --- /dev/null +++ b/src/Core/DevkitConfig.php @@ -0,0 +1,130 @@ + 'kariricode/parser', + * 'namespace' => 'KaririCode\\Parser', + * 'php_version' => '8.4', + * 'phpstan_level' => 9, // 0–9 + * 'psalm_level' => 3, // 1–9 + * 'source_dirs' => ['src'], // relative to project root + * 'test_dirs' => ['tests'], + * 'exclude_dirs' => ['src/Contract'], // excluded from analysis + * 'test_suites' => ['Unit' => 'tests/Unit', 'Integration' => 'tests/Integration'], + * 'coverage_exclude' => ['src/Exception'], + * 'cs_fixer_rules' => [], // MERGED with KaririCode defaults + * 'rector_sets' => [], // REPLACES KaririCode defaults + * 'tools' => [ // version constraints (optional) + * 'phpunit' => '^11.0', + * 'phpstan' => '^2.0', + * 'php-cs-fixer' => '^3.64', + * 'rector' => '^2.0', + * 'psalm' => '^6.0', + * ], + * ]; + * ``` + * + * @since 1.0.0 + */ +final readonly class DevkitConfig +{ + private const string CONFIG_FILE = 'devkit.php'; + + /** @var array */ + public array $overrides; + + public function __construct(string $projectRoot) + { + $configPath = $projectRoot . \DIRECTORY_SEPARATOR . self::CONFIG_FILE; + + if (! is_file($configPath)) { + $this->overrides = []; + + return; + } + + if (! is_readable($configPath)) { + throw ConfigurationException::fileNotReadable($configPath); + } + + $loaded = require $configPath; + + if (! \is_array($loaded)) { + throw ConfigurationException::invalidOverride( + self::CONFIG_FILE, + 'Must return an array.', + ); + } + + /** @var array $loaded */ + $this->overrides = $loaded; + } + + /** + * Get a config value with type-safe fallback. + * + * @template T + * @param T $default + * @return T + */ + public function get(string $key, mixed $default): mixed + { + if (! \array_key_exists($key, $this->overrides)) { + return $default; + } + + $value = $this->overrides[$key]; + + // Type consistency check: override must match default's type + if (null !== $default && \gettype($value) !== \gettype($default)) { + throw ConfigurationException::invalidOverride( + $key, + \sprintf('Expected %s, got %s.', \gettype($default), \gettype($value)), + ); + } + + return $value; + } + + /** @return array */ + public function toolVersions(): array + { + $tools = $this->overrides['tools'] ?? []; + + if (! \is_array($tools)) { + return []; + } + + /** @var array $typed */ + $typed = array_filter( + $tools, + static fn (mixed $v): bool => \is_string($v), + ); + + return $typed; + } + + public function hasOverrides(): bool + { + return [] !== $this->overrides; + } +} diff --git a/src/Core/MigrationDetector.php b/src/Core/MigrationDetector.php new file mode 100644 index 0000000..3d97c58 --- /dev/null +++ b/src/Core/MigrationDetector.php @@ -0,0 +1,109 @@ + $composer */ + $composer = json_decode( + $raw, + true, + 512, + \JSON_THROW_ON_ERROR, + ); + + /** @var array $requireDev */ + $requireDev = $composer['require-dev'] ?? []; + + foreach (self::REPLACED_PACKAGES as $package) { + if (\array_key_exists($package, $requireDev)) { + $redundantPackages[$package] = $requireDev[$package]; + } + } + } + } + + // Scan root-level config files + foreach (self::REPLACED_CONFIG_FILES as $file) { + $fullPath = $projectRoot . \DIRECTORY_SEPARATOR . $file; + if (is_file($fullPath)) { + $redundantConfigFiles[] = $file; + } + } + + // Scan root-level cache paths + foreach (self::REPLACED_CACHE_PATHS as $cachePath) { + $fullPath = $projectRoot . \DIRECTORY_SEPARATOR . $cachePath; + if (file_exists($fullPath)) { + $redundantCachePaths[] = $cachePath; + } + } + + return new MigrationReport( + projectRoot: $projectRoot, + redundantPackages: $redundantPackages, + redundantConfigFiles: $redundantConfigFiles, + redundantCachePaths: $redundantCachePaths, + ); + } +} diff --git a/src/Core/ProcessExecutor.php b/src/Core/ProcessExecutor.php new file mode 100644 index 0000000..c701dab --- /dev/null +++ b/src/Core/ProcessExecutor.php @@ -0,0 +1,109 @@ + $command Full command with arguments. + */ + public function execute(string $toolName, array $command): ToolResult + { + $start = hrtime(true); + + $process = proc_open( + $command, + [ + 0 => ['pipe', 'r'], + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ], + $pipes, + $this->workingDirectory, + ); + + if (! \is_resource($process)) { + return new ToolResult( + toolName: $toolName, + exitCode: 127, + stdout: '', + stderr: 'Failed to spawn process: ' . implode(' ', $command), + elapsedSeconds: 0.0, + ); + } + + fclose($pipes[0]); + + $stdout = (string) stream_get_contents($pipes[1]); + $stderr = (string) stream_get_contents($pipes[2]); + + fclose($pipes[1]); + fclose($pipes[2]); + + $exitCode = proc_close($process); + $elapsed = (hrtime(true) - $start) / 1_000_000_000; + + return new ToolResult( + toolName: $toolName, + exitCode: $exitCode, + stdout: $stdout, + stderr: $stderr, + elapsedSeconds: round($elapsed, 3), + ); + } + + /** + * Resolve a tool binary path using the three-tier strategy. + * + * @param string $vendorBin Relative path like "vendor/bin/phpunit" + */ + public function resolveBinary(string $vendorBin): ?string + { + // Tier 1: PHAR-internal binary + if ('' !== \Phar::running(false)) { + $pharBin = \Phar::running(true) . '/' . $vendorBin; + if (file_exists($pharBin)) { + return $pharBin; + } + } + + // Tier 2: Project-local vendor binary + $localBin = $this->workingDirectory . '/' . $vendorBin; + if (is_file($localBin) && is_executable($localBin)) { + return $localBin; + } + + // Tier 3: Global PATH + $basename = basename($vendorBin); + $globalBin = trim((string) shell_exec('command -v ' . escapeshellarg($basename) . ' 2>/dev/null')); + if ('' !== $globalBin && is_executable($globalBin)) { + return $globalBin; + } + + return null; + } +} diff --git a/src/Core/ProjectContext.php b/src/Core/ProjectContext.php new file mode 100644 index 0000000..186f11d --- /dev/null +++ b/src/Core/ProjectContext.php @@ -0,0 +1,83 @@ + $sourceDirs + * @param list $testDirs + * @param list $excludeDirs Relative to project root + * @param array $testSuites Suite name → relative dir + * @param list $coverageExclude Relative to project root + * @param array $csFixerRules Merged with KaririCode defaults + * @param list $rectorSets + * @param array $toolVersions Tool name → version constraint + */ + public function __construct( + public string $projectRoot, + public string $projectName, + public string $namespace, + public string $phpVersion, + public int $phpstanLevel, + public int $psalmLevel, + public array $sourceDirs, + public array $testDirs, + public array $excludeDirs, + public array $testSuites, + public array $coverageExclude, + public array $csFixerRules, + public array $rectorSets, + public array $toolVersions, + ) { + $this->devkitDir = $projectRoot . \DIRECTORY_SEPARATOR . '.kcode'; + $this->buildDir = $this->devkitDir . \DIRECTORY_SEPARATOR . 'build'; + } + + /** Absolute path to a file inside `.kcode/`. */ + public function configPath(string $filename): string + { + return $this->devkitDir . \DIRECTORY_SEPARATOR . $filename; + } + + /** Absolute path inside `.kcode/build/`. */ + public function buildPath(string $filename = ''): string + { + return $this->buildDir . ('' !== $filename ? \DIRECTORY_SEPARATOR . $filename : ''); + } + + /** @return list Convert absolute source dirs to project-relative paths. */ + public function relativeSourceDirs(): array + { + return array_map(fn (string $dir): string => $this->relativize($dir), $this->sourceDirs); + } + + /** @return list Convert absolute test dirs to project-relative paths. */ + public function relativeTestDirs(): array + { + return array_map(fn (string $dir): string => $this->relativize($dir), $this->testDirs); + } + + public function relativize(string $absolutePath): string + { + $prefix = $this->projectRoot . \DIRECTORY_SEPARATOR; + + return str_starts_with($absolutePath, $prefix) + ? substr($absolutePath, \strlen($prefix)) + : $absolutePath; + } +} diff --git a/src/Core/ProjectDetector.php b/src/Core/ProjectDetector.php new file mode 100644 index 0000000..9562873 --- /dev/null +++ b/src/Core/ProjectDetector.php @@ -0,0 +1,210 @@ + $composer */ + $composer = json_decode( + $raw, + true, + 512, + \JSON_THROW_ON_ERROR, + ); + + // Load overrides from project root devkit.php (not from .kcode/) + $config = new DevkitConfig($workingDirectory); + + /** @var array>> $autoload */ + $autoload = \is_array($composer['autoload'] ?? null) ? $composer['autoload'] : []; + /** @var array>> $autoloadDev */ + $autoloadDev = \is_array($composer['autoload-dev'] ?? null) ? $composer['autoload-dev'] : []; + + /** @var array> $psr4Source */ + $psr4Source = \is_array($autoload['psr-4'] ?? null) ? $autoload['psr-4'] : []; + /** @var array> $psr4Test */ + $psr4Test = \is_array($autoloadDev['psr-4'] ?? null) ? $autoloadDev['psr-4'] : []; + + $sourceDirs = $config->get('source_dirs', null) + ?? $this->detectPsr4Dirs($workingDirectory, $psr4Source, ['src']); + + $testDirs = $config->get('test_dirs', null) + ?? $this->detectPsr4Dirs($workingDirectory, $psr4Test, ['tests']); + + $projectName = isset($composer['name']) && \is_string($composer['name']) + ? $composer['name'] + : basename($workingDirectory); + + return new ProjectContext( + projectRoot: $workingDirectory, + projectName: $config->get('project_name', $projectName), + namespace: $config->get('namespace', $this->detectNamespace($composer)), + phpVersion: $config->get('php_version', $this->detectPhpVersion($composer)), + phpstanLevel: $config->get('phpstan_level', 9), + psalmLevel: $config->get('psalm_level', 3), + sourceDirs: $sourceDirs, + testDirs: $testDirs, + excludeDirs: $config->get('exclude_dirs', ['src/Contract']), + testSuites: $config->get('test_suites', $this->detectTestSuites($workingDirectory, $testDirs)), + coverageExclude: $config->get('coverage_exclude', ['src/Exception']), + csFixerRules: array_merge(self::DEFAULT_CS_RULES, $config->get('cs_fixer_rules', [])), + rectorSets: $config->get('rector_sets', self::DEFAULT_RECTOR_SETS), + toolVersions: $config->toolVersions(), + ); + } + + // ── Detection helpers ───────────────────────────────────────── + + /** @param array $composer */ + private function detectNamespace(array $composer): string + { + $autoload = \is_array($composer['autoload'] ?? null) ? $composer['autoload'] : []; + + /** @var array> $psr4 */ + $psr4 = \is_array($autoload['psr-4'] ?? null) ? $autoload['psr-4'] : []; + + foreach ($psr4 as $ns => $path) { + return rtrim((string) $ns, '\\'); + } + + return 'App'; + } + + /** @param array $composer */ + private function detectPhpVersion(array $composer): string + { + $require = \is_array($composer['require'] ?? null) ? $composer['require'] : []; + $constraint = \is_string($require['php'] ?? null) ? $require['php'] : '^8.4'; + + return preg_match('/(\d+\.\d+)/', $constraint, $m) ? $m[1] : '8.4'; + } + + /** + * @param array> $psr4Map + * @param list $fallbackDirs Context-aware fallback directories. + * @return list Absolute paths + */ + private function detectPsr4Dirs(string $root, array $psr4Map, array $fallbackDirs): array + { + $dirs = []; + + foreach ($psr4Map as $paths) { + foreach ((array) $paths as $path) { + $absolute = $root . \DIRECTORY_SEPARATOR . rtrim((string) $path, '/'); + if (is_dir($absolute)) { + $dirs[] = $absolute; + } + } + } + + // Fallback: use context-aware defaults (source → 'src', test → 'tests') + if ([] === $dirs) { + foreach ($fallbackDirs as $fallback) { + $candidate = $root . \DIRECTORY_SEPARATOR . $fallback; + if (is_dir($candidate)) { + $dirs[] = $candidate; + + break; + } + } + } + + return $dirs; + } + + /** + * @param list $testDirs + * @return array Suite name → relative path + */ + private function detectTestSuites(string $root, array $testDirs): array + { + $suites = []; + $standard = ['Unit', 'Integration', 'Conformance', 'Functional']; + + foreach ($testDirs as $testDir) { + foreach ($standard as $suite) { + $candidate = $testDir . \DIRECTORY_SEPARATOR . $suite; + if (is_dir($candidate)) { + $relative = str_replace($root . \DIRECTORY_SEPARATOR, '', $candidate); + $suites[$suite] = $relative; + } + } + } + + // If nothing detected, register full test dir + if ([] === $suites && [] !== $testDirs) { + $relative = str_replace($root . \DIRECTORY_SEPARATOR, '', $testDirs[0]); + $suites['Default'] = $relative; + } + + return $suites; + } + + // ── KaririCode Defaults ─────────────────────────────────────── + + private const array DEFAULT_CS_RULES = [ + '@PSR12' => true, + '@PHP84Migration' => true, + 'array_syntax' => ['syntax' => 'short'], + 'ordered_imports' => ['sort_algorithm' => 'alpha'], + 'no_unused_imports' => true, + 'trailing_comma_in_multiline' => ['elements' => ['arrays', 'arguments', 'parameters']], + 'phpdoc_scalar' => true, + 'unary_operator_spaces' => true, + 'binary_operator_spaces' => true, + 'blank_line_before_statement' => [ + 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'], + ], + 'class_attributes_separation' => [ + 'elements' => ['method' => 'one', 'property' => 'one'], + ], + 'method_argument_space' => [ + 'on_multiline' => 'ensure_fully_multiline', + 'keep_multiple_spaces_after_comma' => true, + ], + 'single_trait_insert_per_statement' => true, + 'declare_strict_types' => true, + 'native_function_invocation' => [ + 'include' => ['@compiler_optimized'], + 'scope' => 'namespaced', + ], + 'not_operator_with_successor_space' => true, + ]; + + private const array DEFAULT_RECTOR_SETS = [ + 'LevelSetList::UP_TO_PHP_84', + 'SetList::CODE_QUALITY', + 'SetList::DEAD_CODE', + 'SetList::EARLY_RETURN', + 'SetList::TYPE_DECLARATION', + ]; +} diff --git a/src/Email.php b/src/Email.php deleted file mode 100644 index 73d16de..0000000 --- a/src/Email.php +++ /dev/null @@ -1,44 +0,0 @@ - - * @copyright 2025 KaririCode - * @license MIT - * @version 1.0.0 - * @since 1.0.0 - */ -final readonly class Email implements Stringable, JsonSerializable -{ - public function __construct( - #[Length(min: 3, max: 320)] - public string $value, - ) { - if (!filter_var($value, FILTER_VALIDATE_EMAIL)) { - throw new InvalidArgumentException("Invalid email: {$value}"); - } - } - - public function __toString(): string - { - return $this->value; - } - - public function jsonSerialize(): string - { - return $this->value; - } -} diff --git a/src/Exception/ConfigurationException.php b/src/Exception/ConfigurationException.php new file mode 100644 index 0000000..30f9d4c --- /dev/null +++ b/src/Exception/ConfigurationException.php @@ -0,0 +1,23 @@ + - * @copyright 2025 KaririCode - * @license MIT - * @version 1.0.0 - * @since 1.0.0 - */ -#[Attribute(Attribute::TARGET_PROPERTY)] -class Length -{ - public function __construct( - public readonly int $min = 0, - public readonly int $max = PHP_INT_MAX, - ) { - } -} diff --git a/src/Runner/AbstractToolRunner.php b/src/Runner/AbstractToolRunner.php new file mode 100644 index 0000000..34a682d --- /dev/null +++ b/src/Runner/AbstractToolRunner.php @@ -0,0 +1,70 @@ + + */ + abstract protected function defaultArguments(): array; + + #[\Override] + public function isAvailable(): bool + { + return null !== $this->binary(); + } + + /** @param list $arguments */ + #[\Override] + public function run(array $arguments = []): ToolResult + { + $binary = $this->binary(); + + if (null === $binary) { + return new ToolResult( + toolName: $this->toolName(), + exitCode: 127, + stdout: '', + stderr: \sprintf('Binary not found for "%s".', $this->toolName()), + elapsedSeconds: 0.0, + ); + } + + $command = [$binary, ...$this->defaultArguments(), ...$arguments]; + + return $this->executor->execute($this->toolName(), $command); + } + + protected function binary(): ?string + { + return $this->resolvedBinary ??= $this->executor->resolveBinary($this->vendorBin()); + } +} diff --git a/src/Runner/ComposerAuditRunner.php b/src/Runner/ComposerAuditRunner.php new file mode 100644 index 0000000..3727d77 --- /dev/null +++ b/src/Runner/ComposerAuditRunner.php @@ -0,0 +1,50 @@ +/dev/null')); + + if ('' !== $global && is_executable($global)) { + return $global; + } + + return parent::binary(); + } +} diff --git a/src/Runner/CsFixerRunner.php b/src/Runner/CsFixerRunner.php new file mode 100644 index 0000000..03e53db --- /dev/null +++ b/src/Runner/CsFixerRunner.php @@ -0,0 +1,39 @@ +context->configPath('php-cs-fixer.php'), + '--diff', + '--ansi', + ]; + } +} diff --git a/src/Runner/PhpStanRunner.php b/src/Runner/PhpStanRunner.php new file mode 100644 index 0000000..b636ddb --- /dev/null +++ b/src/Runner/PhpStanRunner.php @@ -0,0 +1,37 @@ +context->configPath('phpstan.neon'), + '--no-progress', + '--memory-limit=1G', + ]; + } +} diff --git a/src/Runner/PhpUnitRunner.php b/src/Runner/PhpUnitRunner.php new file mode 100644 index 0000000..b967ccf --- /dev/null +++ b/src/Runner/PhpUnitRunner.php @@ -0,0 +1,34 @@ +context->configPath('phpunit.xml.dist'), + ]; + } +} diff --git a/src/Runner/PsalmRunner.php b/src/Runner/PsalmRunner.php new file mode 100644 index 0000000..5fa914b --- /dev/null +++ b/src/Runner/PsalmRunner.php @@ -0,0 +1,36 @@ +context->configPath('psalm.xml'), + '--no-progress', + '--show-info=false', + ]; + } +} diff --git a/src/Runner/RectorRunner.php b/src/Runner/RectorRunner.php new file mode 100644 index 0000000..e4daced --- /dev/null +++ b/src/Runner/RectorRunner.php @@ -0,0 +1,39 @@ +context->configPath('rector.php'), + '--dry-run', + '--ansi', + ]; + } +} diff --git a/src/UserId.php b/src/UserId.php deleted file mode 100644 index 79b7e21..0000000 --- a/src/UserId.php +++ /dev/null @@ -1,44 +0,0 @@ - - * @copyright 2025 KaririCode - * @license MIT - * @version 1.0.0 - * @since 1.0.0 - */ -final readonly class UserId implements Stringable, JsonSerializable -{ - public function __construct( - #[Length(min: 1, max: 50)] - public string $value, - ) { - if (empty($value)) { - throw new InvalidArgumentException('User ID cannot be empty.'); - } - } - - public function __toString(): string - { - return $this->value; - } - - public function jsonSerialize(): string - { - return $this->value; - } -} diff --git a/src/UserProfile.php b/src/UserProfile.php deleted file mode 100644 index 7cc084e..0000000 --- a/src/UserProfile.php +++ /dev/null @@ -1,327 +0,0 @@ -with2FA('secret') - * ->promote(); - * - * echo $user->displayLabel(); // "Walmir Silva (editor)" - * ``` - * - * @package KaririCode\DevKit - * @category Query Filtering - * @author Walmir Silva - * @copyright 2025 KaririCode - * @license MIT - * @version 1.0.0 - * @since 1.0.0 - */ -final readonly class UserProfile implements JsonSerializable, Stringable -{ - public const string MODEL = 'UserProfile'; - - public const int VERSION = 1; - - public function __construct( - #[Length(min: 1, max: 50)] - public string $id, - #[Length(min: 2, max: 120)] - public string $name, - public Email $email, - public UserRole $role = UserRole::VIEWER, - public UserStatus $status = UserStatus::ACTIVE, - #[SensitiveParameter] - public ?string $twoFactorSecret = null, - public ?array $meta = null, - public ?DateTimeImmutable $createdAt = null, - public ?DateTimeImmutable $updatedAt = null, - ) { - } - - /** - * Creates a new UserProfile instance with a generated ID and current timestamps. - * - * @param string $name The user's full name. - * @param Email|string $email The user's email address. Can be an Email object or a string. - * @param UserRole $role The user's role, defaults to VIEWER. - * @return UserProfile A new instance. - */ - public static function new( - string $name, - Email|string $email, - UserRole $role = UserRole::VIEWER, - ): self { - $now = new DateTimeImmutable(); - - return new self( - id: self::generateId(), - name: $name, - email: $email instanceof Email ? $email : new Email($email), - role: $role, - createdAt: $now, - updatedAt: $now, - ); - } - - /** - * Creates a UserProfile from a raw data array (e.g., from a database or API). - * This is the elegant, declarative hydration method. - * @param array $data An associative array containing user data. Expected keys: - * - 'id' (string, optional): User ID. If not provided, a new one will be generated. - * - 'name' (string): User's full name. - * - 'email' (string|Email): User's email address. - * - 'role' (string|UserRole, optional): User's role. Defaults to 'VIEWER'. - * - 'status' (string|UserStatus, optional): User's status. Defaults to 'ACTIVE'. - * - 'twoFactorSecret' (string, optional): Two-factor authentication secret. - * - 'meta' (array, optional): Additional metadata. - * - 'createdAt' (string|DateTimeImmutable, optional): Creation timestamp. - * - 'updatedAt' (string|DateTimeImmutable, optional): Last update timestamp. - * * @return UserProfile A new UserProfile instance hydrated with the provided data. - * @throws InvalidArgumentException If required data is missing or invalid. - */ - public static function fromArray(array $data): self - { - return new self( - id: $data['id'] ?? self::generateId(), - name: $data['name'] ?? '', - email: self::hydrateEmail($data), - role: self::hydrateRole($data), - status: self::hydrateStatus($data), - twoFactorSecret: $data['twoFactorSecret'] ?? null, - meta: $data['meta'] ?? null, - createdAt: self::hydrateDate($data, 'createdAt'), - updatedAt: self::hydrateDate($data, 'updatedAt'), - ); - } - - // ---------------------------------------------------------- - // Query Methods - // ---------------------------------------------------------- - - public function canEdit(): bool - { - return $this->status->isActive() && $this->role->canEdit(); - } - - public function has2FA(): bool - { - return $this->twoFactorSecret !== null; - } - - // ---------------------------------------------------------- - // Domain Behavior - // ---------------------------------------------------------- - - public function promote(): self - { - $newRole = $this->role === UserRole::ADMIN ? UserRole::ADMIN : UserRole::EDITOR; - - return new self( - id: $this->id, - name: $this->name, - email: $this->email, - role: $newRole, - status: $this->status, - twoFactorSecret: $this->twoFactorSecret, - meta: $this->meta, - createdAt: $this->createdAt, - updatedAt: new DateTimeImmutable(), - ); - } - - public function suspend(): self - { - return new self( - id: $this->id, - name: $this->name, - email: $this->email, - role: $this->role, - status: UserStatus::SUSPENDED, - twoFactorSecret: $this->twoFactorSecret, - meta: $this->meta, - createdAt: $this->createdAt, - updatedAt: new DateTimeImmutable(), - ); - } - - public function with2FA(#[SensitiveParameter] string $secret): self - { - return new self( - id: $this->id, - name: $this->name, - email: $this->email, - role: $this->role, - status: $this->status, - twoFactorSecret: $secret, - meta: $this->meta, - createdAt: $this->createdAt, - updatedAt: new DateTimeImmutable(), - ); - } - - public function withMeta(array $meta): self - { - return new self( - id: $this->id, - name: $this->name, - email: $this->email, - role: $this->role, - status: $this->status, - twoFactorSecret: $this->twoFactorSecret, - meta: $meta, - createdAt: $this->createdAt, - updatedAt: new DateTimeImmutable(), - ); - } - - public function displayLabel(): string - { - return "{$this->name} ({$this->role->label()})"; - } - - // ---------------------------------------------------------- - // Serialization - // ---------------------------------------------------------- - - public function jsonSerialize(): array - { - return [ - 'model' => self::MODEL, - 'version' => self::VERSION, - 'id' => $this->id, - 'name' => $this->name, - 'email' => (string) $this->email, - 'role' => $this->role->value, - 'status' => $this->status->value, - 'has2FA' => $this->has2FA(), - 'meta' => $this->meta, - 'createdAt' => $this->createdAt?->format('c'), - 'updatedAt' => $this->updatedAt?->format('c'), - ]; - } - - public function __toString(): string - { - return sprintf('%s#%s<%s>', self::MODEL, $this->id, $this->role->value); - } - - // ---------------------------------------------------------- - // Helpers - // ---------------------------------------------------------- - - /** - * Converts 'email' data from an array into an Email object. - */ - private static function hydrateEmail(array $data): Email - { - $emailInput = $data['email'] ?? ''; - - return $emailInput instanceof Email ? $emailInput : new Email($emailInput); - } - - /** - * Converts 'role' data from an array into a UserRole enum. - * Uses UserRole::tryFrom for safe conversion, defaulting to VIEWER. - */ - private static function hydrateRole(array $data): UserRole - { - $role = $data['role'] ?? null; - - if ($role instanceof UserRole) { - return $role; - } - - if (is_string($role) && $role !== '') { - return UserRole::tryFrom($role) ?? UserRole::VIEWER; - } - - return UserRole::VIEWER; - } - - /** - * Converts 'status' data from an array into a UserStatus enum. - * Uses UserStatus::tryFrom for safe conversion, defaulting to ACTIVE. - */ - private static function hydrateStatus(array $data): UserStatus - { - $status = $data['status'] ?? null; - - if ($status instanceof UserStatus) { - return $status; - } - - if (is_string($status) && $status !== '') { - return UserStatus::tryFrom($status) ?? UserStatus::ACTIVE; - } - - return UserStatus::ACTIVE; - } - - /** - * Converts a date string from an array into a DateTimeImmutable object. - * Returns null if the key is missing, empty, or the date is invalid. - */ - private static function hydrateDate(array $data, string $key): ?DateTimeImmutable - { - if (empty($data[$key])) { - return null; - } - - if ($data[$key] instanceof DateTimeImmutable) { - return $data[$key]; - } - - try { - return new DateTimeImmutable($data[$key]); - } catch (Exception $e) { - // Log error if needed: error_log("Failed to hydrate date '{$key}': {$e->getMessage()}"); - return null; - } - } - - /** - * Generates a random ID. - * Uses a static *variable* for caching the Randomizer, - * which is allowed in readonly classes. - */ - private static function generateId(): string - { - static $rng = null; - $rng ??= new Randomizer(); - - return bin2hex($rng->getBytes(8)); - } -} diff --git a/src/UserRole.php b/src/UserRole.php deleted file mode 100644 index f1ceaef..0000000 --- a/src/UserRole.php +++ /dev/null @@ -1,62 +0,0 @@ -with2FA('secret') - * ->promote(); - * - * echo $user->displayLabel(); // "Walmir Silva (editor)" - * ``` - * - * @package KaririCode\DevKit - * @category Query Filtering - * @author Walmir Silva - * @copyright 2025 KaririCode - * @license MIT - * @version 1.0.0 - * @since 1.0.0 - */ -enum UserRole: string -{ - case ADMIN = 'ADMIN'; - case EDITOR = 'EDITOR'; - case VIEWER = 'VIEWER'; - - public function canEdit(): bool - { - return match ($this) { - self::ADMIN, self::EDITOR => true, - self::VIEWER => false, - }; - } - - public function label(): string - { - return match ($this) { - self::ADMIN => 'admin', - self::EDITOR => 'editor', - self::VIEWER => 'viewer', - }; - } -} diff --git a/src/UserStatus.php b/src/UserStatus.php deleted file mode 100644 index 7d6fb9b..0000000 --- a/src/UserStatus.php +++ /dev/null @@ -1,29 +0,0 @@ - - * @copyright 2025 KaririCode - * @license MIT - * @version 1.0.0 - * @since 1.0.0 - */ -enum UserStatus: string -{ - case ACTIVE = 'ACTIVE'; - case SUSPENDED = 'SUSPENDED'; - - public function isActive(): bool - { - return $this === self::ACTIVE; - } -} diff --git a/src/ValueObject/MigrationReport.php b/src/ValueObject/MigrationReport.php new file mode 100644 index 0000000..397205a --- /dev/null +++ b/src/ValueObject/MigrationReport.php @@ -0,0 +1,153 @@ + $redundantPackages Package name → version constraint + * @param list $redundantConfigFiles Filenames relative to project root + * @param list $redundantCachePaths Cache paths relative to project root + */ + public function __construct( + public string $projectRoot, + public array $redundantPackages, + public array $redundantConfigFiles, + public array $redundantCachePaths, + ) { + $this->totalItems = \count($redundantPackages) + + \count($redundantConfigFiles) + + \count($redundantCachePaths); + $this->hasRedundancies = $this->totalItems > 0; + } + + public function hasPackages(): bool + { + return [] !== $this->redundantPackages; + } + + public function hasConfigFiles(): bool + { + return [] !== $this->redundantConfigFiles; + } + + public function hasCachePaths(): bool + { + return [] !== $this->redundantCachePaths; + } + + /** Remove redundant config files and cache paths from disk. */ + public function removeFiles(): int + { + $removed = 0; + + foreach ([...$this->redundantConfigFiles, ...$this->redundantCachePaths] as $relative) { + $fullPath = $this->projectRoot . \DIRECTORY_SEPARATOR . $relative; + + if (is_dir($fullPath)) { + $this->removeRecursive($fullPath); + ++$removed; + } elseif (is_file($fullPath)) { + unlink($fullPath); + ++$removed; + } + } + + return $removed; + } + + /** + * Remove redundant packages from composer.json require-dev. + * + * Rewrites composer.json in place preserving JSON formatting. + * + * @return list Package names actually removed. + */ + public function removePackagesFromComposer(): array + { + $composerPath = $this->projectRoot . \DIRECTORY_SEPARATOR . 'composer.json'; + + if (! is_file($composerPath)) { + return []; + } + + $raw = file_get_contents($composerPath); + + if (false === $raw) { + return []; + } + + /** @var array $composer */ + $composer = json_decode($raw, true, 512, \JSON_THROW_ON_ERROR); + + $removed = []; + + /** @var array $requireDev */ + $requireDev = \is_array($composer['require-dev'] ?? null) ? $composer['require-dev'] : []; + + foreach (array_keys($this->redundantPackages) as $package) { + if (isset($requireDev[$package])) { + unset($requireDev[$package]); + $removed[] = $package; + } + } + + if ([] === $removed) { + return []; + } + + // Write back the updated require-dev (or remove the key if empty) + if ([] === $requireDev) { + unset($composer['require-dev']); + } else { + $composer['require-dev'] = $requireDev; + } + + // Detect indentation: 4-space (default) or tab + $jsonFlags = \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE; + $encoded = json_encode($composer, $jsonFlags); + + if (false === $encoded) { + return []; + } + + // Re-apply tab indentation if original used tabs + if (str_contains($raw, "\t")) { + $encoded = str_replace(' ', "\t", $encoded); + } + + file_put_contents( + $composerPath, + $encoded . \PHP_EOL, + ); + + return $removed; + } + + private function removeRecursive(string $dir): void + { + $items = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS), + \RecursiveIteratorIterator::CHILD_FIRST, + ); + + foreach ($items as $item) { + /** @var \SplFileInfo $item */ + $item->isDir() ? rmdir($item->getPathname()) : unlink($item->getPathname()); + } + + rmdir($dir); + } +} diff --git a/src/ValueObject/QualityReport.php b/src/ValueObject/QualityReport.php new file mode 100644 index 0000000..f290ec4 --- /dev/null +++ b/src/ValueObject/QualityReport.php @@ -0,0 +1,43 @@ + $results */ + public function __construct( + public array $results, + ) { + $this->passed = array_all($results, static fn (ToolResult $r): bool => $r->success); + $this->totalSeconds = array_sum(array_map( + static fn (ToolResult $r): float => $r->elapsedSeconds, + $results, + )); + $this->failureCount = \count(array_filter( + $results, + static fn (ToolResult $r): bool => ! $r->success, + )); + } + + /** @return list */ + public function failures(): array + { + return array_values(array_filter( + $this->results, + static fn (ToolResult $r): bool => ! $r->success, + )); + } +} diff --git a/src/ValueObject/ToolResult.php b/src/ValueObject/ToolResult.php new file mode 100644 index 0000000..b90b9fe --- /dev/null +++ b/src/ValueObject/ToolResult.php @@ -0,0 +1,32 @@ +success = 0 === $exitCode; + } + + public function output(): string + { + $combined = trim($this->stdout . "\n" . $this->stderr); + + return '' !== $combined ? $combined : '(no output)'; + } +} diff --git a/tests/Integration/UserProfileFlowTest.php b/tests/Integration/UserProfileFlowTest.php deleted file mode 100644 index 424c260..0000000 --- a/tests/Integration/UserProfileFlowTest.php +++ /dev/null @@ -1,91 +0,0 @@ - can edit. - * 3. Activates 2FA. - * 4. Verifies the final state and JSON serialization. - */ - #[Test] - public function itHandlesACompleteUserLifecycleFlow(): void - { - // --- 1) Simulate DB load (VIEWER + ACTIVE) --- - $dbData = [ - 'id' => 'flow-user-001', - 'name' => 'Utilizador de Fluxo', - 'email' => 'flow@exemplo.com', - 'role' => 'VIEWER', - 'status' => 'ACTIVE', - 'createdAt' => '2025-01-01T10:00:00+00:00', - 'updatedAt' => '2025-01-01T10:00:00+00:00', - ]; - - $user = UserProfile::fromArray($dbData); - - // Initial assertions - $this->assertSame(UserRole::VIEWER, $user->role); - $this->assertSame(UserStatus::ACTIVE, $user->status); - $this->assertFalse($user->has2FA()); - $this->assertFalse($user->canEdit(), 'Viewer ativo não deve poder editar.'); - - // --- 2) Business logic: promote (VIEWER -> EDITOR). Now can edit. --- - usleep(10); - $promotedUser = $user->promote(); - - $this->assertNotSame($user, $promotedUser); // immutable - $this->assertSame(UserRole::VIEWER, $user->role); // original intact - $this->assertSame(UserRole::EDITOR, $promotedUser->role); - $this->assertSame(UserStatus::ACTIVE, $promotedUser->status); - $this->assertTrue($promotedUser->canEdit(), 'Editor ativo deve poder editar.'); - $this->assertNotEquals($user->updatedAt, $promotedUser->updatedAt); - - // --- 3) Business logic: enable 2FA (still EDITOR + ACTIVE). --- - usleep(10); - $secureUser = $promotedUser->with2FA('segredo-super-secreto-123'); - - $this->assertNotSame($promotedUser, $secureUser); // immutable - $this->assertFalse($promotedUser->has2FA()); // previous intact - $this->assertTrue($secureUser->has2FA()); - $this->assertSame(UserRole::EDITOR, $secureUser->role); - $this->assertSame(UserStatus::ACTIVE, $secureUser->status); - $this->assertTrue($secureUser->canEdit(), 'Editor ativo deve poder editar.'); - $this->assertNotEquals($promotedUser->updatedAt, $secureUser->updatedAt); - - // --- 4) Final state + serialization checks --- - $finalJson = $secureUser->jsonSerialize(); - - $this->assertSame('UserProfile', $finalJson['model']); - $this->assertSame('flow-user-001', $finalJson['id']); - $this->assertSame('Utilizador de Fluxo', $finalJson['name']); - $this->assertSame('EDITOR', $finalJson['role']); - $this->assertSame('ACTIVE', $finalJson['status']); - $this->assertTrue($finalJson['has2FA']); - $this->assertSame('2025-01-01T10:00:00+00:00', $finalJson['createdAt']); - - // updatedAt must be newer than createdAt - $this->assertGreaterThan( - new DateTimeImmutable($finalJson['createdAt']), - new DateTimeImmutable($finalJson['updatedAt']), - ); - } -} diff --git a/tests/Unit/Core/DevkitConfigTest.php b/tests/Unit/Core/DevkitConfigTest.php new file mode 100644 index 0000000..23ea761 --- /dev/null +++ b/tests/Unit/Core/DevkitConfigTest.php @@ -0,0 +1,131 @@ +tmpDir = sys_get_temp_dir() . '/devkit_config_test_' . uniqid(); + mkdir($this->tmpDir, 0777, true); + } + + protected function tearDown(): void + { + // Clean up devkit.php if left + $configPath = $this->tmpDir . '/devkit.php'; + if (file_exists($configPath)) { + unlink($configPath); + } + if (is_dir($this->tmpDir)) { + rmdir($this->tmpDir); + } + } + + #[Test] + public function withoutConfigFileOverridesAreEmpty(): void + { + $config = new DevkitConfig($this->tmpDir); + + $this->assertFalse($config->hasOverrides()); + $this->assertSame([], $config->overrides); + } + + #[Test] + public function getReturnsDefaultWhenKeyNotSet(): void + { + $config = new DevkitConfig($this->tmpDir); + + $this->assertSame(9, $config->get('phpstan_level', 9)); + $this->assertSame('8.4', $config->get('php_version', '8.4')); + } + + #[Test] + public function withValidConfigFileOverridesAreLoaded(): void + { + file_put_contents( + $this->tmpDir . '/devkit.php', + " 5, 'php_version' => '8.3'];", + ); + + $config = new DevkitConfig($this->tmpDir); + + $this->assertTrue($config->hasOverrides()); + $this->assertSame(5, $config->get('phpstan_level', 9)); + $this->assertSame('8.3', $config->get('php_version', '8.4')); + } + + #[Test] + public function getReturnsDefaultForUnknownKeyEvenWithConfigFile(): void + { + file_put_contents( + $this->tmpDir . '/devkit.php', + " 5];", + ); + + $config = new DevkitConfig($this->tmpDir); + + $this->assertSame(3, $config->get('psalm_level', 3)); + } + + #[Test] + public function getThrowsConfigurationExceptionOnTypeMismatch(): void + { + file_put_contents( + $this->tmpDir . '/devkit.php', + " 'nine'];", // string instead of int + ); + + $config = new DevkitConfig($this->tmpDir); + + $this->expectException(ConfigurationException::class); + $config->get('phpstan_level', 9); // expects int, got string + } + + #[Test] + public function invalidConfigFileThrowsConfigurationException(): void + { + file_put_contents( + $this->tmpDir . '/devkit.php', + "expectException(ConfigurationException::class); + new DevkitConfig($this->tmpDir); + } + + #[Test] + public function toolVersionsReturnsEmptyArrayWhenNotConfigured(): void + { + $config = new DevkitConfig($this->tmpDir); + + $this->assertSame([], $config->toolVersions()); + } + + #[Test] + public function toolVersionsReturnsToolsArrayFromConfig(): void + { + file_put_contents( + $this->tmpDir . '/devkit.php', + " ['phpunit' => '^11.0', 'phpstan' => '^2.0']];", + ); + + $config = new DevkitConfig($this->tmpDir); + + $this->assertSame( + ['phpunit' => '^11.0', 'phpstan' => '^2.0'], + $config->toolVersions(), + ); + } +} diff --git a/tests/Unit/Core/ProjectContextTest.php b/tests/Unit/Core/ProjectContextTest.php new file mode 100644 index 0000000..74ec259 --- /dev/null +++ b/tests/Unit/Core/ProjectContextTest.php @@ -0,0 +1,102 @@ +root = '/var/www/my-project'; + $this->context = new ProjectContext( + projectRoot: $this->root, + projectName: 'kariricode/parser', + namespace: 'KaririCode\\Parser', + phpVersion: '8.4', + phpstanLevel: 9, + psalmLevel: 3, + sourceDirs: [$this->root . '/src'], + testDirs: [$this->root . '/tests'], + excludeDirs: ['src/Contract'], + testSuites: ['Unit' => 'tests/Unit'], + coverageExclude: ['src/Exception'], + csFixerRules: ['@PSR12' => true], + rectorSets: ['LevelSetList::UP_TO_PHP_84'], + toolVersions: [], + ); + } + + #[Test] + public function devkitDirIsComposedCorrectly(): void + { + $this->assertSame($this->root . DIRECTORY_SEPARATOR . '.kcode', $this->context->devkitDir); + } + + #[Test] + public function buildDirIsInsideDevkitDir(): void + { + $expected = $this->root . DIRECTORY_SEPARATOR . '.kcode' . DIRECTORY_SEPARATOR . 'build'; + $this->assertSame($expected, $this->context->buildDir); + } + + #[Test] + public function configPathReturnsAbsolutePathInsideDevkitDir(): void + { + $path = $this->context->configPath('phpunit.xml'); + $this->assertSame($this->root . '/.kcode/phpunit.xml', $path); + } + + #[Test] + public function buildPathWithFilenameReturnsFullPath(): void + { + $path = $this->context->buildPath('kcode.phar'); + $this->assertStringEndsWith('kcode.phar', $path); + $this->assertStringContainsString('.kcode/build', $path); + } + + #[Test] + public function buildPathWithoutFilenameReturnsBuildDir(): void + { + $path = $this->context->buildPath(); + $this->assertSame($this->context->buildDir, $path); + } + + #[Test] + public function relativeSourceDirsReturnProjectRelativePaths(): void + { + $relative = $this->context->relativeSourceDirs(); + $this->assertSame(['src'], $relative); + } + + #[Test] + public function relativeTestDirsReturnProjectRelativePaths(): void + { + $relative = $this->context->relativeTestDirs(); + $this->assertSame(['tests'], $relative); + } + + #[Test] + public function relativizeStripsProjectRootPrefix(): void + { + $absolute = $this->root . '/src/Core/Devkit.php'; + $relative = $this->context->relativize($absolute); + $this->assertSame('src/Core/Devkit.php', $relative); + } + + #[Test] + public function relativizeReturnsPathUnchangedWhenNotUnderRoot(): void + { + $external = '/some/other/path/file.php'; + $this->assertSame($external, $this->context->relativize($external)); + } +} diff --git a/tests/Unit/EnumsTest.php b/tests/Unit/EnumsTest.php deleted file mode 100644 index 89d0848..0000000 --- a/tests/Unit/EnumsTest.php +++ /dev/null @@ -1,65 +0,0 @@ - - */ - public static function roleEditProvider(): array - { - return [ - 'Admin pode editar' => [UserRole::ADMIN, true], - 'Editor pode editar' => [UserRole::EDITOR, true], - 'Viewer não pode editar' => [UserRole::VIEWER, false], - ]; - } - - /** - * Tests the edit permission logic. - */ - #[Test] - #[DataProvider('roleEditProvider')] - public function userRoleCanEdit(UserRole $role, bool $expectedResult): void - { - $this->assertSame($expectedResult, $role->canEdit()); - } - - /** - * Tests the labels of the roles. - */ - #[Test] - public function userRoleLabels(): void - { - $this->assertSame('admin', UserRole::ADMIN->label()); - $this->assertSame('editor', UserRole::EDITOR->label()); - $this->assertSame('viewer', UserRole::VIEWER->label()); - } - - /** - * Tests the 'active' status logic. - */ - #[Test] - public function userStatusIsActive(): void - { - $this->assertTrue(UserStatus::ACTIVE->isActive()); - $this->assertFalse(UserStatus::SUSPENDED->isActive()); - } -} diff --git a/tests/Unit/Exception/ConfigurationExceptionTest.php b/tests/Unit/Exception/ConfigurationExceptionTest.php new file mode 100644 index 0000000..7ae1044 --- /dev/null +++ b/tests/Unit/Exception/ConfigurationExceptionTest.php @@ -0,0 +1,46 @@ +assertInstanceOf(ConfigurationException::class, $ex); + $this->assertStringContainsString($key, $ex->getMessage()); + $this->assertStringContainsString($reason, $ex->getMessage()); + } + + #[Test] + public function fileNotReadableContainsPathInMessage(): void + { + $path = '/etc/shadow'; + $ex = ConfigurationException::fileNotReadable($path); + + $this->assertInstanceOf(ConfigurationException::class, $ex); + $this->assertStringContainsString($path, $ex->getMessage()); + } + + #[Test] + public function exceptionExtendsDevkitException(): void + { + $ex = ConfigurationException::invalidOverride('key', 'reason'); + + $this->assertInstanceOf(DevkitException::class, $ex); + } +} diff --git a/tests/Unit/Exception/DevkitExceptionTest.php b/tests/Unit/Exception/DevkitExceptionTest.php new file mode 100644 index 0000000..b3ff102 --- /dev/null +++ b/tests/Unit/Exception/DevkitExceptionTest.php @@ -0,0 +1,43 @@ +assertInstanceOf(DevkitException::class, $ex); + $this->assertStringContainsString($path, $ex->getMessage()); + $this->assertStringContainsString('composer.json', $ex->getMessage()); + } + + #[Test] + public function directoryNotWritableContainsPathInMessage(): void + { + $path = '/some/readonly/dir'; + $ex = DevkitException::directoryNotWritable($path); + + $this->assertInstanceOf(DevkitException::class, $ex); + $this->assertStringContainsString($path, $ex->getMessage()); + } + + #[Test] + public function exceptionExtendsRuntimeException(): void + { + $ex = DevkitException::projectNotDetected('/foo'); + + $this->assertInstanceOf(\RuntimeException::class, $ex); + } +} diff --git a/tests/Unit/Exception/ToolExceptionTest.php b/tests/Unit/Exception/ToolExceptionTest.php new file mode 100644 index 0000000..eb74bae --- /dev/null +++ b/tests/Unit/Exception/ToolExceptionTest.php @@ -0,0 +1,53 @@ +assertInstanceOf(ToolException::class, $ex); + $this->assertStringContainsString('phpunit', $ex->getMessage()); + } + + #[Test] + public function executionFailedContainsToolNameExitCodeAndOutput(): void + { + $ex = ToolException::executionFailed('phpstan', 1, 'Analysis failed'); + + $this->assertInstanceOf(ToolException::class, $ex); + $this->assertStringContainsString('phpstan', $ex->getMessage()); + $this->assertStringContainsString('1', $ex->getMessage()); + $this->assertStringContainsString('Analysis failed', $ex->getMessage()); + $this->assertSame(1, $ex->getCode()); + } + + #[Test] + public function executionFailedWithEmptyOutputUsesNoOutputPlaceholder(): void + { + $ex = ToolException::executionFailed('rector', 2, ''); + + $this->assertStringContainsString('(no output)', $ex->getMessage()); + $this->assertSame(2, $ex->getCode()); + } + + #[Test] + public function exceptionExtendsDevkitException(): void + { + $ex = ToolException::binaryNotFound('phpunit'); + + $this->assertInstanceOf(DevkitException::class, $ex); + } +} diff --git a/tests/Unit/UserProfileTest.php b/tests/Unit/UserProfileTest.php deleted file mode 100644 index 95d808e..0000000 --- a/tests/Unit/UserProfileTest.php +++ /dev/null @@ -1,239 +0,0 @@ -assertInstanceOf(UserProfile::class, $user); - $this->assertSame('Utilizador Teste', $user->name); - $this->assertSame('teste@exemplo.com', (string) $user->email); - $this->assertSame(UserRole::EDITOR, $user->role); // Verifies the role - $this->assertSame(UserStatus::ACTIVE, $user->status); // Verifies the default status - $this->assertNotNull($user->id); - $this->assertNotNull($user->createdAt); - $this->assertNotNull($user->updatedAt); - $this->assertNull($user->twoFactorSecret); - } - - /** - * Tests creation from an array. - */ - #[Test] - public function itHydratesFromArray(): void - { - $expectedCreated = new DateTimeImmutable('2025-10-22T22:03:20+00:00'); - $data = [ - 'id' => 'user-123', - 'name' => 'Nome do Array', - 'email' => 'array@exemplo.com', - 'role' => 'ADMIN', - 'status' => 'SUSPENDED', - 'twoFactorSecret' => 'segredo123', - 'meta' => ['key' => 'value'], - 'createdAt' => $expectedCreated->format('c'), - ]; - - $user = UserProfile::fromArray($data); - - $this->assertSame('user-123', $user->id); - $this->assertSame('Nome do Array', $user->name); - $this->assertSame('array@exemplo.com', (string) $user->email); - $this->assertSame(UserRole::ADMIN, $user->role); - $this->assertSame(UserStatus::SUSPENDED, $user->status); - $this->assertSame('segredo123', $user->twoFactorSecret); - $this->assertSame(['key' => 'value'], $user->meta); - $this->assertSame( - $expectedCreated->format('Y-m-d\TH:i:sP'), - $user->createdAt?->format('Y-m-d\TH:i:sP'), - ); - - $this->assertNull($user->updatedAt); - } - - /** - * Tests default values when hydrating with minimal data. - */ - #[Test] - public function itHydratesFromArrayWithDefaults(): void - { - $data = [ - 'name' => 'Utilizador Mínimo', - 'email' => 'minimo@exemplo.com', - ]; - - $user = UserProfile::fromArray($data); - - $this->assertNotNull($user->id); // ID is generated - $this->assertSame('Utilizador Mínimo', $user->name); - $this->assertSame(UserRole::VIEWER, $user->role); // Default role - $this->assertSame(UserStatus::ACTIVE, $user->status); // Default status - $this->assertNull($user->createdAt); - } - - /** - * Tests if 'fromArray' fails with an invalid email. - */ - #[Test] - public function itFailsHydrationWithInvalidEmail(): void - { - $data = [ - 'name' => 'Utilizador Falhado', - 'email' => 'email-invalido', - ]; - - // The exception comes from the Email constructor - $this->expectException(InvalidArgumentException::class); - UserProfile::fromArray($data); - } - - /** - * Tests 'with*' methods to ensure immutability. - * The UserProfile class is 'readonly', but 'with*' methods - * create NEW instances. - */ - #[Test] - public function itIsImmutableAndCreatesNewInstances(): void - { - $user1 = UserProfile::new('Original', 'original@exemplo.com'); - $user1CreatedAt = $user1->createdAt; - - // A microsecond delay is needed to ensure the timestamp changes - usleep(10); - - // 1. Promote - $user2 = $user1->promote(); - $this->assertNotSame($user1, $user2); // They are different objects - $this->assertSame(UserRole::VIEWER, $user1->role); // Original does not change - $this->assertSame(UserRole::EDITOR, $user2->role); // New object changes - $this->assertNotEquals($user1->updatedAt, $user2->updatedAt); - $this->assertEquals($user1CreatedAt, $user2->createdAt); // createdAt is maintained - - // 2. Suspend - $user3 = $user2->suspend(); - $this->assertNotSame($user2, $user3); - $this->assertSame(UserStatus::ACTIVE, $user2->status); // Previous does not change - $this->assertSame(UserStatus::SUSPENDED, $user3->status); // New object changes - - // 3. Add 2FA - $user4 = $user3->with2FA('segredo'); - $this->assertNotSame($user3, $user4); - $this->assertNull($user3->twoFactorSecret); // Previous does not change - $this->assertSame('segredo', $user4->twoFactorSecret); - $this->assertTrue($user4->has2FA()); - - // 4. Add Meta - $user5 = $user4->withMeta(['foo' => 'bar']); - $this->assertNotSame($user4, $user5); - $this->assertNull($user4->meta); // Previous does not change - $this->assertSame(['foo' => 'bar'], $user5->meta); - } - - /** - * Testa a lógica de promoção (não deve promover quem já é admin). - */ - #[Test] - public function itPromotesCorrectly(): void - { - $viewer = UserProfile::new('Viewer', 'v@v.com', UserRole::VIEWER); - $editor = UserProfile::new('Editor', 'e@e.com', UserRole::EDITOR); - $admin = UserProfile::new('Admin', 'a@a.com', UserRole::ADMIN); - - // Viewer is promoted to Editor - $this->assertSame(UserRole::EDITOR, $viewer->promote()->role); - // Editor remains Editor - $this->assertSame(UserRole::EDITOR, $editor->promote()->role); - // Admin remains Admin - $this->assertSame(UserRole::ADMIN, $admin->promote()->role); - } - - /** - * Provides data for the 'canEdit' test. - */ - public static function editPermissionProvider(): array - { - return [ - // Role, Status, Expected - 'Admin Ativo' => [UserRole::ADMIN, UserStatus::ACTIVE, true], - 'Editor Ativo' => [UserRole::EDITOR, UserStatus::ACTIVE, true], - 'Viewer Ativo' => [UserRole::VIEWER, UserStatus::ACTIVE, false], - 'Admin Suspenso' => [UserRole::ADMIN, UserStatus::SUSPENDED, false], - 'Editor Suspenso' => [UserRole::EDITOR, UserStatus::SUSPENDED, false], - 'Viewer Suspenso' => [UserRole::VIEWER, UserStatus::SUSPENDED, false], - ]; - } - - /** - * Tests the 'canEdit' logic (combination of Role and Status). - */ - #[Test] - #[DataProvider('editPermissionProvider')] - public function itChecksEditPermissions(UserRole $role, UserStatus $status, bool $expected): void - { - // We use fromArray to "force" the status - $user = UserProfile::fromArray([ - 'name' => 'Teste Permissão', - 'email' => 'p@p.com', - 'role' => $role, - 'status' => $status, - ]); - - $this->assertSame($expected, $user->canEdit()); - } - - /** - * Tests serialization outputs. - */ - #[Test] - public function itSerializesCorrectly(): void - { - $user = UserProfile::new('Serializar', 's@s.com'); - $id = $user->id; - - // Test __toString - $this->assertSame("UserProfile#{$id}", (string) $user); - - // Teste displayLabel - $this->assertSame('Serializar (viewer)', $user->displayLabel()); - - // Teste jsonSerialize - $json = $user->jsonSerialize(); - - $this->assertSame('UserProfile', $json['model']); - $this->assertSame(1, $json['version']); - $this->assertSame($id, $json['id']); - $this->assertSame('Serializar', $json['name']); - $this->assertSame('s@s.com', $json['email']); - $this->assertSame('VIEWER', $json['role']); - $this->assertSame('ACTIVE', $json['status']); - $this->assertFalse($json['has2FA']); - $this->assertNotNull($json['createdAt']); - } -} diff --git a/tests/Unit/ValueObject/MigrationReportTest.php b/tests/Unit/ValueObject/MigrationReportTest.php new file mode 100644 index 0000000..5d6f541 --- /dev/null +++ b/tests/Unit/ValueObject/MigrationReportTest.php @@ -0,0 +1,88 @@ +assertFalse($report->hasRedundancies); + $this->assertSame(0, $report->totalItems); + $this->assertFalse($report->hasPackages()); + $this->assertFalse($report->hasConfigFiles()); + $this->assertFalse($report->hasCachePaths()); + } + + #[Test] + public function hasRedundanciesIsTrueWhenThereAreRedundantPackages(): void + { + $report = new MigrationReport( + projectRoot: '/app', + redundantPackages: ['phpunit/phpunit' => '^11.0'], + redundantConfigFiles: [], + redundantCachePaths: [], + ); + + $this->assertTrue($report->hasRedundancies); + $this->assertSame(1, $report->totalItems); + $this->assertTrue($report->hasPackages()); + $this->assertFalse($report->hasConfigFiles()); + } + + #[Test] + public function totalItemsCountsAllCategories(): void + { + $report = new MigrationReport( + projectRoot: '/app', + redundantPackages: ['phpstan/phpstan' => '^2.0', 'vimeo/psalm' => '^6.0'], + redundantConfigFiles: ['phpstan.neon', 'phpcs.xml'], + redundantCachePaths: ['.phpunit.cache'], + ); + + $this->assertSame(5, $report->totalItems); + $this->assertTrue($report->hasRedundancies); + $this->assertTrue($report->hasPackages()); + $this->assertTrue($report->hasConfigFiles()); + $this->assertTrue($report->hasCachePaths()); + } + + #[Test] + public function removeFilesDeletesExistingFiles(): void + { + $tmpDir = sys_get_temp_dir() . '/devkit_test_' . uniqid(); + mkdir($tmpDir, 0777, true); + + $fileToRemove = $tmpDir . '/phpstan.neon'; + file_put_contents($fileToRemove, 'parameters:'); + + $report = new MigrationReport( + projectRoot: $tmpDir, + redundantPackages: [], + redundantConfigFiles: ['phpstan.neon'], + redundantCachePaths: [], + ); + + $removed = $report->removeFiles(); + + $this->assertSame(1, $removed); + $this->assertFileDoesNotExist($fileToRemove); + + rmdir($tmpDir); + } +} diff --git a/tests/Unit/ValueObject/QualityReportTest.php b/tests/Unit/ValueObject/QualityReportTest.php new file mode 100644 index 0000000..40f5ed2 --- /dev/null +++ b/tests/Unit/ValueObject/QualityReportTest.php @@ -0,0 +1,89 @@ +makeResult('phpunit', 0, 1.0), + $this->makeResult('phpstan', 0, 0.5), + ]); + + $this->assertTrue($report->passed); + $this->assertSame(0, $report->failureCount); + } + + #[Test] + public function passedIsFalseWhenAtLeastOneToolFails(): void + { + $report = new QualityReport([ + $this->makeResult('phpunit', 0, 1.0), + $this->makeResult('phpstan', 1, 0.5), + ]); + + $this->assertFalse($report->passed); + $this->assertSame(1, $report->failureCount); + } + + #[Test] + public function totalSecondsIsTheSumOfAllElapsedTimes(): void + { + $report = new QualityReport([ + $this->makeResult('phpunit', 0, 1.5), + $this->makeResult('phpstan', 0, 0.7), + $this->makeResult('psalm', 0, 0.3), + ]); + + $this->assertEqualsWithDelta(2.5, $report->totalSeconds, 0.001); + } + + #[Test] + public function failuresReturnsOnlyFailedResults(): void + { + $passing = $this->makeResult('phpunit', 0); + $failing1 = $this->makeResult('phpstan', 1); + $failing2 = $this->makeResult('psalm', 2); + + $report = new QualityReport([$passing, $failing1, $failing2]); + + $failures = $report->failures(); + + $this->assertCount(2, $failures); + $this->assertSame($failing1, $failures[0]); + $this->assertSame($failing2, $failures[1]); + } + + #[Test] + public function emptyReportPassesWithZeroTotals(): void + { + $report = new QualityReport([]); + + $this->assertTrue($report->passed); + $this->assertSame(0, $report->failureCount); + $this->assertSame(0.0, $report->totalSeconds); + $this->assertEmpty($report->failures()); + } +} diff --git a/tests/Unit/ValueObject/ToolResultTest.php b/tests/Unit/ValueObject/ToolResultTest.php new file mode 100644 index 0000000..4567af4 --- /dev/null +++ b/tests/Unit/ValueObject/ToolResultTest.php @@ -0,0 +1,89 @@ +assertTrue($result->success); + $this->assertSame('phpunit', $result->toolName); + $this->assertSame(0, $result->exitCode); + $this->assertSame(1.234, $result->elapsedSeconds); + } + + #[Test] + public function successIsFalseWhenExitCodeIsNonZero(): void + { + $result = new ToolResult( + toolName: 'phpstan', + exitCode: 1, + stdout: '', + stderr: 'Found 3 errors', + elapsedSeconds: 0.5, + ); + + $this->assertFalse($result->success); + } + + #[Test] + public function outputCombinesStdoutAndStderr(): void + { + $result = new ToolResult( + toolName: 'phpstan', + exitCode: 1, + stdout: 'Line 1', + stderr: 'Error here', + elapsedSeconds: 0.1, + ); + + $output = $result->output(); + $this->assertStringContainsString('Line 1', $output); + $this->assertStringContainsString('Error here', $output); + } + + #[Test] + public function outputReturnsPlaceholderWhenBothStreamsAreEmpty(): void + { + $result = new ToolResult( + toolName: 'rector', + exitCode: 0, + stdout: '', + stderr: '', + elapsedSeconds: 0.0, + ); + + $this->assertSame('(no output)', $result->output()); + } + + #[Test] + public function outputTrimsWhitespace(): void + { + $result = new ToolResult( + toolName: 'psalm', + exitCode: 0, + stdout: " passed \n", + stderr: ' ', + elapsedSeconds: 0.2, + ); + + $this->assertSame('passed', $result->output()); + } +} diff --git a/tests/Unit/ValueObjectsTest.php b/tests/Unit/ValueObjectsTest.php deleted file mode 100644 index 5f7ae1b..0000000 --- a/tests/Unit/ValueObjectsTest.php +++ /dev/null @@ -1,74 +0,0 @@ -assertSame('teste@exemplo.com', $email->value); - // Verify string casting and JSON serialization - $this->assertSame('teste@exemplo.com', (string) $email); - $this->assertSame('teste@exemplo.com', $email->jsonSerialize()); - } - - #[Test] - public function emailThrowsExceptionForInvalidValue(): void - { - // Expect an InvalidArgumentException to be thrown for an invalid email format - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Invalid email: email-invalido'); - - // Attempt to create an Email object with an invalid string, which should trigger the exception - new Email('email-invalido'); - } - - /** - * Tests that a UserId object is successfully created with a valid, non-empty ID string. - * It also verifies the __toString() and jsonSerialize() methods. - */ - #[Test] - public function userIdIsCreatedWithValidValue(): void - { - $id = new UserId('id-12345'); - $this->assertSame('id-12345', $id->value); - // Verify string casting and JSON serialization - $this->assertSame('id-12345', (string) $id); - $this->assertSame('id-12345', $id->jsonSerialize()); - } - - /** - * Tests that a UserId object throws an exception when initialized with an empty string. - */ - #[Test] - public function userIdThrowsExceptionForEmptyValue(): void - { - // Expect an InvalidArgumentException to be thrown for an empty user ID - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('User ID cannot be empty.'); - - new UserId(''); // Isto deve falhar - } -}