| # Makefile for folio project | |
| # Variables | |
| SHELL := /bin/bash | |
| PYTHON := python3 | |
| SCRIPTS_DIR := scripts | |
| LOGS_DIR := logs | |
| TIMESTAMP := $(shell date +%Y%m%d_%H%M%S) | |
| PORT := 5000 | |
| POETRY := poetry | |
| # Default target | |
| .PHONY: help | |
| help: | |
| @echo "Available targets:" | |
| @echo " help - Show this help message" | |
| @echo " env - Set up Poetry and create a virtual environment" | |
| @echo " install - Install dependencies and set script permissions" | |
| @echo " hooks - Install git hooks for pre-commit checks" | |
| @echo " folio - Start the portfolio dashboard with debug mode enabled" | |
| @echo " Options: portfolio=path/to/file.csv (use custom portfolio file)" | |
| @echo " log=LEVEL (set logging level: DEBUG, INFO, WARNING, ERROR)" | |
| @echo " focli - Start the interactive Folio CLI shell for portfolio analysis" | |
| @echo " clean - Clean up generated files and caches" | |
| @echo " Options: --cache (also clear data cache)" | |
| @echo " lint - Run type checker and linter" | |
| @echo " Options: --fix (auto-fix linting issues)" | |
| @echo " test - Run all unit tests in the tests directory" | |
| @echo " test-e2e - Run end-to-end tests against real portfolio data" | |
| @echo "" | |
| @echo "Note: All targets now use Poetry under the hood for dependency management" | |
| @echo "" | |
| @echo "Docker targets:" | |
| @echo " docker-build - Build the Docker image" | |
| @echo " docker-run - Run the Docker container" | |
| @echo " docker-up - Start the application with docker-compose" | |
| @echo " docker-down - Stop the docker-compose services" | |
| @echo " docker-logs - Tail the Docker logs" | |
| @echo " docker-test - Run tests in a Docker container" | |
| # Set up virtual environment | |
| .PHONY: env | |
| env: | |
| @echo "Setting up virtual environment with Poetry..." | |
| @if ! command -v $(POETRY) &> /dev/null; then \ | |
| echo "Poetry not found. Installing..."; \ | |
| curl -sSL https://install.python-poetry.org | $(PYTHON) -; \ | |
| fi | |
| @$(POETRY) config virtualenvs.in-project true | |
| @echo "Creating Poetry virtual environment..." | |
| @$(POETRY) env use $(PYTHON) | |
| @echo "Virtual environment created successfully." | |
| @echo "NOTE: To activate the virtual environment in your current shell, run: poetry shell" | |
| @echo "The virtual environment will be automatically activated for all make commands." | |
| # Install dependencies | |
| .PHONY: install | |
| install: | |
| @echo "Installing dependencies..." | |
| @if ! command -v $(POETRY) &> /dev/null; then \ | |
| echo "Poetry not found. Please run 'make env' first."; \ | |
| exit 1; \ | |
| fi | |
| @mkdir -p $(LOGS_DIR) | |
| @(echo "=== Installation Log $(TIMESTAMP) ===" && \ | |
| echo "Starting installation at: $$(date)" && \ | |
| $(POETRY) install && \ | |
| echo "Setting script permissions..." && \ | |
| chmod +x $(SCRIPTS_DIR)/*.sh && \ | |
| chmod +x $(SCRIPTS_DIR)/*.py && \ | |
| echo "Installation complete at: $$(date)") | tee $(LOGS_DIR)/install_$(TIMESTAMP).log | |
| @echo "Installation log saved to: $(LOGS_DIR)/install_$(TIMESTAMP).log" | |
| @echo "To install git hooks, run 'make hooks'" | |
| # Install git hooks | |
| .PHONY: hooks | |
| hooks: | |
| @echo "Installing git hooks..." | |
| @if ! command -v $(POETRY) &> /dev/null; then \ | |
| echo "Poetry not found. Please run 'make env' first."; \ | |
| exit 1; \ | |
| fi | |
| @$(POETRY) run pre-commit install -c .pre-commit-config.yaml --hook-type pre-commit | |
| @$(POETRY) run pre-commit install -c .pre-push-config.yaml --hook-type pre-push | |
| @echo "Git hooks installed successfully!" | |
| # Clean up generated files | |
| .PHONY: clean | |
| clean: | |
| @echo "Cleaning up generated files..." | |
| @bash $(SCRIPTS_DIR)/clean.sh | |
| @find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true | |
| @find . -type d -name ".mypy_cache" -exec rm -rf {} + 2>/dev/null || true | |
| @find . -type d -name ".ruff_cache" -exec rm -rf {} + 2>/dev/null || true | |
| @if [ "$(findstring --cache,$(MAKECMDGOALS))" != "" ]; then \ | |
| echo "Clearing data cache..."; \ | |
| rm -rf cache/*; \ | |
| mkdir -p cache; \ | |
| echo "Cache cleared."; \ | |
| fi | |
| # Lint Python code | |
| .PHONY: lint | |
| lint: | |
| @echo "Running linter (ruff)..." | |
| @if ! command -v $(POETRY) &> /dev/null; then \ | |
| echo "Poetry not found. Please run 'make env' first."; \ | |
| exit 1; \ | |
| fi | |
| @mkdir -p $(LOGS_DIR) | |
| @(echo "=== Code Check Log $(TIMESTAMP) ===" && \ | |
| echo "Starting checks at: $$(date)" && \ | |
| $(POETRY) run ruff check --fix src/ tests/ \ | |
| 2>&1) | tee $(LOGS_DIR)/code_check_latest.log | |
| @echo "Check log saved to: $(LOGS_DIR)/code_check_latest.log" | |
| # Allow --fix as target without actions | |
| .PHONY: --fix | |
| --fix: | |
| # Portfolio and CLI Projects | |
| .PHONY: folio stop-folio port focli | |
| # Poetry is used under the hood for all targets | |
| # Docker targets | |
| .PHONY: docker-build docker-run docker-up docker-down docker-logs docker-compose-up docker-compose-down docker-test deploy-hf | |
| folio: | |
| @echo "Starting portfolio dashboard with debug mode..." | |
| @if ! command -v $(POETRY) &> /dev/null; then \ | |
| echo "Poetry not found. Please run 'make env' first."; \ | |
| exit 1; \ | |
| fi | |
| @LOG_LEVEL=$(if $(log),$(log),INFO) \ | |
| $(POETRY) run python -m src.folio.app --port 8051 --debug $(if $(portfolio),--portfolio $(portfolio),) | |
| stop-folio: | |
| @echo "Stopping portfolio dashboard..." | |
| @PIDS=$$(ps aux | grep "[p]ython.*folio" | awk '{print $$2}'); \ | |
| if [ -n "$$PIDS" ]; then \ | |
| echo "Found folio processes with PIDs: $$PIDS"; \ | |
| for PID in $$PIDS; do \ | |
| echo "Killing process $$PID..."; \ | |
| kill -9 $$PID 2>/dev/null || echo "Failed to kill process $$PID (might require sudo)"; \ | |
| done; \ | |
| echo "All folio processes have been terminated."; \ | |
| else \ | |
| echo "No running folio processes found."; \ | |
| fi | |
| focli: | |
| @echo "Starting Folio CLI interactive shell..." | |
| @if ! command -v $(POETRY) &> /dev/null; then \ | |
| echo "Poetry not found. Please run 'make env' first."; \ | |
| exit 1; \ | |
| fi | |
| @$(POETRY) run python src/focli/focli.py | |
| # Test targets | |
| .PHONY: test test-e2e | |
| test: | |
| @echo "Running unit tests..." | |
| @if ! command -v $(POETRY) &> /dev/null; then \ | |
| echo "Poetry not found. Please run 'make env' first."; \ | |
| exit 1; \ | |
| fi | |
| @mkdir -p $(LOGS_DIR) | |
| @(echo "=== Test Run Log $(TIMESTAMP) ===" && \ | |
| echo "Starting tests at: $$(date)" && \ | |
| $(POETRY) run pytest tests/ -v 2>&1) | tee $(LOGS_DIR)/test_latest.log | |
| @echo "Test log saved to: $(LOGS_DIR)/test_latest.log" | |
| test-e2e: | |
| @echo "Running end-to-end tests..." | |
| @if ! command -v $(POETRY) &> /dev/null; then \ | |
| echo "Poetry not found. Please run 'make env' first."; \ | |
| exit 1; \ | |
| fi | |
| @if [ ! -f "private-data/test/test-portfolio.csv" ]; then \ | |
| echo "Warning: Test portfolio file not found at private-data/test/test-portfolio.csv"; \ | |
| echo "E2E tests will try to use sample-data/sample-portfolio.csv instead."; \ | |
| fi | |
| @mkdir -p $(LOGS_DIR) | |
| @(echo "=== E2E Test Run Log $(TIMESTAMP) ===" && \ | |
| echo "Starting E2E tests at: $$(date)" && \ | |
| $(POETRY) run pytest tests/e2e/ -v 2>&1) | tee $(LOGS_DIR)/test_e2e_latest.log | |
| @echo "E2E test log saved to: $(LOGS_DIR)/test_e2e_latest.log" | |
| # Docker commands | |
| docker-build: | |
| @echo "Building Docker image..." | |
| docker build --debug -t folio:latest . | |
| # Run the Docker container | |
| docker-run: | |
| @echo "Running Docker container..." | |
| docker run -p 8050:8050 --env-file .env folio:latest | |
| # Start with docker-compose | |
| docker-up: | |
| @echo "Starting with docker-compose..." | |
| docker-compose up -d | |
| @echo "Folio app launched successfully!" | |
| @echo "Access the app at: http://localhost:8060" | |
| # Stop docker-compose services | |
| docker-down: | |
| @echo "Stopping docker-compose services..." | |
| docker-compose down | |
| # Alias for backward compatibility | |
| docker-compose-up: docker-up | |
| docker-compose-down: docker-down | |
| # Tail Docker logs | |
| docker-logs: | |
| @echo "Tailing Docker logs..." | |
| docker-compose logs -f | |
| # Run tests in Docker container | |
| docker-test: | |
| @echo "Running tests in Docker container..." | |
| @if [ -z "$$GEMINI_API_KEY" ]; then \ | |
| echo "Warning: GEMINI_API_KEY environment variable not set. Some tests may fail."; \ | |
| fi | |
| @docker-compose -f docker-compose.test.yml build --build-arg INSTALL_DEV=true | |
| @docker-compose -f docker-compose.test.yml run --rm folio | |
| # Deploy to Hugging Face Spaces | |
| deploy-hf: | |
| @echo "Deploying to Hugging Face Spaces..." | |
| @echo "Checking if Hugging Face Space remote exists..." | |
| @if ! git remote | grep -q "space"; then \ | |
| echo "Adding Hugging Face Space remote..."; \ | |
| git remote add space git@hf.co:spaces/mingdom/folio; \ | |
| fi | |
| @echo "Pushing to Hugging Face Space..." | |
| @git push space main:main | |
| @echo "\n✅ Deployment to Hugging Face Space completed!" | |
| @echo "Your application is now available at: https://huggingface.co/spaces/mingdom/folio" | |
| %: | |
| @: | |