# UnitForge Makefile # Development workflow automation using uv package manager .PHONY: help install install-dev clean test test-cov lint format type-check .PHONY: server cli demo docker-build docker-dev docker-prod docker-test .PHONY: docker-push docker-pull docker-tag docker-login registry-build registry-push .PHONY: pre-commit setup-dev deps-update deps-check security-check .PHONY: build package publish docs release validate-config # Default target .DEFAULT_GOAL := help # Colors for output (load from centralized utility) include scripts/colors.mk # Configuration PYTHON_VERSION := 3.8 PROJECT_NAME := unitforge VENV_PATH := .venv UV_INSTALLED := $(shell command -v uv 2> /dev/null) help: ## Show this help message $(call header,UnitForge Development Makefile) @echo "" @echo -e "$(GREEN)Available targets:$(NC)" @awk 'BEGIN {FS = ":.*##"} /^[a-zA-Z_-]+:.*##/ { printf " $(YELLOW)%-20s$(NC) %s\n", $$1, $$2 }' $(MAKEFILE_LIST) @echo "" @echo -e "$(GREEN)Quick start:$(NC)" @echo " make setup-dev # Set up development environment" @echo " make server # Start development server" @echo " make test # Run tests" @echo "" check-uv: ## Check if uv is installed ifndef UV_INSTALLED $(call error,uv is not installed) @echo "Install it with: curl -LsSf https://astral.sh/uv/install.sh | sh" @exit 1 else $(call success,uv is available) endif setup-dev: check-uv ## Set up development environment with uv $(call info,Setting up development environment...) @if [ -d "$(VENV_PATH)" ]; then \ echo -e "$(YELLOW)$(WARNING_SYMBOL)$(NC) Removing existing virtual environment..."; \ rm -rf $(VENV_PATH); \ fi uv venv --python python3 $(call success,Virtual environment created) @$(MAKE) install-dev @if [ ! -f ".env" ] && [ -f ".env.example" ]; then \ echo -e "$(BLUE)$(INFO_SYMBOL)$(NC) Creating .env file from template..."; \ cp .env.example .env; \ echo -e "$(GREEN)$(SUCCESS_SYMBOL)$(NC) .env file created. Please review and customize the settings."; \ fi @if [ -f ".pre-commit-config.yaml" ]; then \ echo -e "$(BLUE)$(INFO_SYMBOL)$(NC) Installing pre-commit hooks..."; \ . $(VENV_PATH)/bin/activate && pre-commit install; \ echo -e "$(GREEN)$(SUCCESS_SYMBOL)$(NC) Pre-commit hooks installed"; \ fi $(call success,Development environment ready!) @echo "" @echo -e "$(YELLOW)Next steps:$(NC)" @echo " source $(VENV_PATH)/bin/activate # Activate environment" @echo " make server # Start development server" @echo " make test # Run tests" install: check-uv ## Install production dependencies $(call info,Installing production dependencies...) uv pip install -e . $(call success,Production dependencies installed) install-dev: check-uv ## Install development dependencies $(call info,Installing development dependencies...) uv pip install -e ".[dev,web]" $(call success,Development dependencies installed) deps-update: check-uv ## Update all dependencies $(call info,Updating dependencies...) uv pip install --upgrade -e ".[dev,web]" $(call success,Dependencies updated) deps-check: ## Check for dependency vulnerabilities $(call info,Checking dependencies for vulnerabilities...) @if [ -f "$(VENV_PATH)/bin/activate" ]; then \ . $(VENV_PATH)/bin/activate && uv run pip-audit --desc || true; \ else \ echo -e "$(YELLOW)$(WARNING_SYMBOL)$(NC) Virtual environment not found. Run 'make setup-dev' first."; \ fi clean: ## Clean up cache files and build artifacts $(call info,Cleaning up...) find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true find . -type f -name "*.pyc" -delete 2>/dev/null || true find . -type f -name "*.pyo" -delete 2>/dev/null || true find . -type f -name "*.orig" -delete 2>/dev/null || true rm -rf .pytest_cache/ htmlcov/ .coverage .mypy_cache/ dist/ build/ rm -rf *.egg-info/ rm -f *.service *.timer *.socket *.mount *.target *.path $(call success,Cleanup complete) test: ## Run tests $(call info,Running tests...) @if [ -f "$(VENV_PATH)/bin/activate" ]; then \ . $(VENV_PATH)/bin/activate && uv run pytest tests/ -v; \ else \ uv run pytest tests/ -v; \ fi test-cov: ## Run tests with coverage $(call info,Running tests with coverage...) @if [ -f "$(VENV_PATH)/bin/activate" ]; then \ . $(VENV_PATH)/bin/activate && uv run pytest tests/ --cov=backend --cov-report=html --cov-report=term; \ else \ uv run pytest tests/ --cov=backend --cov-report=html --cov-report=term; \ fi $(call success,Coverage report generated in htmlcov/) test-watch: ## Run tests in watch mode $(call info,Running tests in watch mode...) @if [ -f "$(VENV_PATH)/bin/activate" ]; then \ . $(VENV_PATH)/bin/activate && uv run pytest tests/ -v --watch; \ else \ uv run pytest tests/ -v --watch; \ fi lint: ## Run linters $(call info,Running linters...) @if [ -f "$(VENV_PATH)/bin/activate" ]; then \ . $(VENV_PATH)/bin/activate && \ uv run black --check backend/ tests/ && \ uv run isort --check-only backend/ tests/ && \ uv run flake8 backend/ tests/ --max-line-length=88 --extend-ignore=E203,W503 --exclude=.venv,__pycache__,build,dist; \ else \ uv run black --check backend/ tests/ && \ uv run isort --check-only backend/ tests/ && \ uv run flake8 backend/ tests/ --max-line-length=88 --extend-ignore=E203,W503 --exclude=.venv,__pycache__,build,dist; \ fi $(call success,Linting passed) format: ## Format code $(call info,Formatting code...) @if [ -f "$(VENV_PATH)/bin/activate" ]; then \ . $(VENV_PATH)/bin/activate && \ uv run black backend/ tests/ && \ uv run isort backend/ tests/; \ else \ uv run black backend/ tests/ && \ uv run isort backend/ tests/; \ fi $(call success,Code formatted) type-check: ## Run type checking $(call info,Running type checks...) @if [ -f "$(VENV_PATH)/bin/activate" ]; then \ . $(VENV_PATH)/bin/activate && PYTHONPATH=. uv run mypy backend/app/ backend/cli/ --ignore-missing-imports; \ else \ PYTHONPATH=. uv run mypy backend/app/ backend/cli/ --ignore-missing-imports; \ fi $(call success,Type checking passed) security-check: ## Run security checks $(call info,Running security checks...) @if [ -f "$(VENV_PATH)/bin/activate" ]; then \ . $(VENV_PATH)/bin/activate && uv run bandit -r backend/ -x tests/; \ else \ uv run bandit -r backend/ -x tests/; \ fi $(call success,Security checks passed) pre-commit: ## Run pre-commit hooks on all files $(call info,Running pre-commit hooks...) @if [ -f "$(VENV_PATH)/bin/activate" ]; then \ . $(VENV_PATH)/bin/activate && uv run pre-commit run --all-files; \ else \ uv run pre-commit run --all-files; \ fi server: ## Start development server $(call info,Starting development server...) @if [ -f "$(VENV_PATH)/bin/activate" ]; then \ . $(VENV_PATH)/bin/activate && ./start-server.sh --log-level debug; \ else \ ./start-server.sh --log-level debug; \ fi server-prod: ## Start production server $(call info,Starting production server...) @if [ -f "$(VENV_PATH)/bin/activate" ]; then \ . $(VENV_PATH)/bin/activate && ./start-server.sh --no-reload --log-level info; \ else \ ./start-server.sh --no-reload --log-level info; \ fi cli: ## Run CLI with help $(call header,UnitForge CLI) @if [ -f "$(VENV_PATH)/bin/activate" ]; then \ . $(VENV_PATH)/bin/activate && ./unitforge-cli --help; \ else \ ./unitforge-cli --help; \ fi demo: ## Run interactive demo $(call header,UnitForge Demo) @if [ -f "$(VENV_PATH)/bin/activate" ]; then \ . $(VENV_PATH)/bin/activate && ./demo.sh; \ else \ ./demo.sh; \ fi validate-config: ## Validate environment configuration $(call header,Validating Configuration) @if [ -f "$(VENV_PATH)/bin/activate" ]; then \ . $(VENV_PATH)/bin/activate && python scripts/validate_config.py; \ else \ python3 scripts/validate_config.py; \ fi # Docker targets DOCKER_PLATFORMS := linux/amd64,linux/arm64 docker-build: ## Build Docker images (docker-compose) $(call info,Building Docker images with docker-compose...) docker-compose build $(call success,Docker images built) docker-buildx-setup: ## Ensure docker buildx builder exists and is bootstrapped $(call info,Setting up docker buildx builder...) @docker buildx create --name unitforge-builder --use 2>/dev/null || true @docker buildx inspect --bootstrap $(call success,Buildx builder ready) docker-buildx: docker-buildx-setup ## Build and push multi-arch image (linux/amd64, linux/arm64) $(call info,Building multi-arch image with buildx...) @if [ -f ".env" ]; then \ REGISTRY_URL=$$(grep '^CONTAINER_REGISTRY_URL=' .env | cut -d'=' -f2 | sed 's|^https\?://||'); \ CONTAINER_TAG=$$(grep '^CONTAINER_TAG=' .env | cut -d'=' -f2); \ docker buildx build --platform $(DOCKER_PLATFORMS) -t $${REGISTRY_URL}:$${CONTAINER_TAG} -t $${REGISTRY_URL}:latest --push -f Dockerfile .; \ else \ echo -e "$(YELLOW)$(WARNING_SYMBOL)$(NC) .env file not found. Set CONTAINER_REGISTRY_URL and CONTAINER_TAG to enable push."; \ fi $(call success,Multi-arch image built and pushed) docker-buildx-local: docker-buildx-setup ## Build for current arch locally (no push) $(call info,Building local image for current architecture...) docker buildx build --load -t unitforge:local -f Dockerfile . $(call success,Local image built: unitforge:local) # Container Registry targets docker-tag: ## Tag Docker images with registry URL $(call info,Tagging Docker images for registry...) @if [ -f ".env" ]; then \ REGISTRY_URL=$$(grep '^CONTAINER_REGISTRY_URL=' .env | cut -d'=' -f2 | sed 's|^https\?://||') && \ CONTAINER_TAG=$$(grep '^CONTAINER_TAG=' .env | cut -d'=' -f2) && \ docker tag unitforge-unitforge-prod:latest $${REGISTRY_URL}:$${CONTAINER_TAG} && \ docker tag unitforge-unitforge-prod:latest $${REGISTRY_URL}:latest; \ else \ echo -e "$(YELLOW)$(WARNING_SYMBOL)$(NC) .env file not found. Using default values."; \ docker tag unitforge-unitforge-prod:latest http://gitea-http.taildb3494.ts.net/will/unitforge:latest; \ fi $(call success,Docker images tagged for registry) docker-push: docker-tag ## Push Docker images to registry $(call info,Pushing Docker images to registry...) @if [ -f ".env" ]; then \ REGISTRY_URL=$$(grep '^CONTAINER_REGISTRY_URL=' .env | cut -d'=' -f2 | sed 's|^https\?://||') && \ CONTAINER_TAG=$$(grep '^CONTAINER_TAG=' .env | cut -d'=' -f2) && \ docker push $${REGISTRY_URL}:$${CONTAINER_TAG} && \ docker push $${REGISTRY_URL}:latest; \ else \ echo -e "$(YELLOW)$(WARNING_SYMBOL)$(NC) .env file not found. Using default values."; \ docker push http://gitea-http.taildb3494.ts.net/will/unitforge:latest; \ fi $(call success,Docker images pushed to registry) docker-pull: ## Pull Docker images from registry $(call info,Pulling Docker images from registry...) @if [ -f ".env" ]; then \ REGISTRY_URL=$$(grep '^CONTAINER_REGISTRY_URL=' .env | cut -d'=' -f2 | sed 's|^https\?://||') && \ CONTAINER_TAG=$$(grep '^CONTAINER_TAG=' .env | cut -d'=' -f2) && \ docker pull $${REGISTRY_URL}:$${CONTAINER_TAG}; \ else \ echo -e "$(YELLOW)$(WARNING_SYMBOL)$(NC) .env file not found. Using default values."; \ docker pull http://gitea-http.taildb3494.ts.net/will/unitforge:latest; \ fi $(call success,Docker images pulled from registry) registry-build: docker-build docker-tag ## Build and tag images for registry $(call success,Images built and tagged for registry) registry-push: registry-build docker-push ## Build, tag, and push images to registry $(call success,Images built, tagged, and pushed to registry) docker-login: ## Login to container registry (interactive) $(call info,Logging into container registry...) @if [ -f ".env" ]; then \ REGISTRY_URL=$$(grep '^CONTAINER_REGISTRY_URL=' .env | cut -d'=' -f2 | sed 's|^https\?://||') && \ echo "Logging into registry at: $${REGISTRY_URL}"; \ docker login $${REGISTRY_URL}; \ else \ echo -e "$(YELLOW)$(WARNING_SYMBOL)$(NC) .env file not found. Using default values."; \ docker login http://gitea-http.taildb3494.ts.net; \ fi $(call success,Logged into container registry) docker-dev: ## Start development environment with Docker $(call info,Starting development environment with Docker...) docker-compose up unitforge-dev docker-prod: ## Start production environment with Docker $(call info,Starting production environment with Docker...) docker-compose up unitforge-prod docker-cli: ## Run CLI in Docker $(call info,Running CLI in Docker...) docker-compose --profile cli run --rm unitforge-cli --help docker-logs: ## Show logs from all Docker services $(call info,Showing Docker logs...) docker-compose logs -f docker-shell: ## Open shell in development container $(call info,Opening shell in development container...) docker-compose exec unitforge-dev /bin/bash docker-clean: ## Clean up Docker containers and volumes $(call info,Cleaning up Docker resources...) docker-compose down --remove-orphans --volumes docker system prune -f $(call success,Docker cleanup completed) docker-clean-registry: ## Remove locally cached registry images $(call info,Cleaning registry images from local cache...) @if [ -f ".env" ]; then \ REGISTRY_URL=$$(grep '^CONTAINER_REGISTRY_URL=' .env | cut -d'=' -f2 | sed 's|^https\?://||') && \ CONTAINER_TAG=$$(grep '^CONTAINER_TAG=' .env | cut -d'=' -f2) && \ docker rmi $${REGISTRY_URL}:$${CONTAINER_TAG} 2>/dev/null || true && \ docker rmi $${REGISTRY_URL}:latest 2>/dev/null || true; \ else \ docker rmi http://gitea-http.taildb3494.ts.net/will/unitforge:latest 2>/dev/null || true; \ fi $(call success,Registry images cleaned from local cache) # Package and release targets build: clean ## Build package $(call info,Building package...) @if [ -f "$(VENV_PATH)/bin/activate" ]; then \ . $(VENV_PATH)/bin/activate && uv run python -m build; \ else \ uv run python -m build; \ fi $(call success,Package built in dist/) package: build ## Create distribution packages $(call info,Creating distribution packages...) @ls -la dist/ $(call success,Distribution packages ready) publish-test: package ## Publish to TestPyPI $(call info,Publishing to TestPyPI...) @if [ -f "$(VENV_PATH)/bin/activate" ]; then \ . $(VENV_PATH)/bin/activate && uv run twine upload --repository testpypi dist/*; \ else \ uv run twine upload --repository testpypi dist/*; \ fi publish: package ## Publish to PyPI $(call info,Publishing to PyPI...) $(call warning,This will publish to the real PyPI. Are you sure? [y/N]) @read -r confirm && [ "$$confirm" = "y" ] || [ "$$confirm" = "Y" ] || (echo "Aborted" && exit 1) @if [ -f "$(VENV_PATH)/bin/activate" ]; then \ . $(VENV_PATH)/bin/activate && uv run twine upload dist/*; \ else \ uv run twine upload dist/*; \ fi docs: ## Generate documentation $(call info,Generating documentation...) $(call warning,Documentation generation not yet implemented) # Comprehensive quality check check-all: lint security-check test ## Run all quality checks $(call success,All quality checks passed) # Release preparation release-check: check-all build ## Prepare for release $(call info,Performing release checks...) $(call success,Release checks completed) @echo "" @echo -e "$(YELLOW)Release checklist:$(NC)" @echo " □ Update version in pyproject.toml" @echo " □ Update CHANGELOG.md" @echo " □ Commit changes" @echo " □ Create git tag" @echo " □ Run 'make publish'" # Quick development cycle dev: format lint test ## Quick development cycle (format, lint, test) $(call success,Development cycle complete) # Initialize new development environment init: setup-dev ## Initialize new development environment $(call success,Development environment initialized) @echo "" @echo -e "$(BLUE)Try these commands:$(NC)" @echo " make server # Start web server" @echo " make cli # Try CLI tool" @echo " make demo # Interactive demo" @echo " make test # Run tests" # Show project status status: ## Show project status $(call header,UnitForge Project Status) @echo "" @echo -e "$(YELLOW)Virtual Environment:$(NC)" @if [ -d "$(VENV_PATH)" ]; then \ echo " ✓ Virtual environment exists at $(VENV_PATH)"; \ if [ -n "$$VIRTUAL_ENV" ]; then \ echo " ✓ Virtual environment is activated"; \ else \ echo " ⚠ Virtual environment not activated"; \ fi \ else \ echo " ✗ Virtual environment not found"; \ fi @echo "" @echo -e "$(YELLOW)Dependencies:$(NC)" @if command -v uv >/dev/null 2>&1; then \ echo " ✓ uv package manager available"; \ else \ echo " ✗ uv package manager not found - REQUIRED"; \ fi @if command -v docker >/dev/null 2>&1; then \ echo " ✓ Docker available"; \ else \ echo " ✗ Docker not found"; \ fi @echo "" @echo -e "$(YELLOW)Container Registry:$(NC)" @if [ -f ".env" ]; then \ REGISTRY_URL=$$(grep '^CONTAINER_REGISTRY_URL=' .env | cut -d'=' -f2) && \ CONTAINER_TAG=$$(grep '^CONTAINER_TAG=' .env | cut -d'=' -f2) && \ echo " ✓ Registry URL: $${REGISTRY_URL}"; \ echo " ✓ Container Tag: $${CONTAINER_TAG}"; \ else \ echo " ⚠ .env file not found - using defaults"; \ fi @echo "" @echo -e "$(YELLOW)Project Files:$(NC)" @if [ -f "pyproject.toml" ]; then echo " ✓ pyproject.toml"; else echo " ✗ pyproject.toml"; fi @if [ -f "unitforge-cli" ]; then echo " ✓ CLI tool"; else echo " ✗ CLI tool"; fi @if [ -f "start-server.sh" ]; then echo " ✓ Server script"; else echo " ✗ Server script"; fi @if [ -d "backend" ]; then echo " ✓ Backend code"; else echo " ✗ Backend code"; fi @if [ -d "frontend" ]; then echo " ✓ Frontend code"; else echo " ✗ Frontend code"; fi @if [ -d "tests" ]; then echo " ✓ Tests"; else echo " ✗ Tests"; fi