Ctrl K
ComunidadGuías…

Makefile

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
.PHONY: help install start update-translation check-translation test test-sequential test-mysql test-postgres test-filter test-filter-mysql test-filter-postgres test-coverage test-coverage-filter test-tia test-tia-baseline backup-test lint-check lint-fix lint migrate migrate-fresh migrate-fresh-seed db-seed setup clean import-db docs docs-build release

# Colors for output
GREEN  := $(shell printf '\033[0;32m')
YELLOW := $(shell printf '\033[0;33m')
NC     := $(shell printf '\033[0m')

# Docker / PHP helpers
#
# The stack runs in one Compose project, started from the main checkout, which
# bind-mounts the repo at /app. A git worktree carries its own copy of
# docker-compose.yml, so a bare `docker compose` there starts a *second*
# project named after the worktree directory -- with no running containers, and
# host ports already taken by the first. Pointing Compose at the main checkout
# keeps every target on the one running project, and the worktree is reachable
# inside it because the mount covers the whole repo. Both values fall back to
# the plain main-checkout case: `/app`, and no --project-directory.
COMPOSE_ROOT := $(patsubst %/.git,%,$(shell git rev-parse --path-format=absolute --git-common-dir 2>/dev/null))

DOCKER_COMPOSE := docker compose $(if $(COMPOSE_ROOT),--project-directory $(COMPOSE_ROOT))
PHP_SERVICE    := app
PHP_WORKDIR    := /app$(if $(COMPOSE_ROOT),$(subst $(COMPOSE_ROOT),,$(CURDIR)))

# Forward AI-agent env vars (Claude Code, Cursor, Gemini, ...) into the container so
# laravel/pao detects the agent and emits compact JSON output instead of verbose logs.
# `-e VAR` is only added when VAR is set on the host, avoiding empty-string false positives.
AGENT_ENV := $(foreach v,CLAUDECODE CLAUDE_CODE AI_AGENT CURSOR_AGENT GEMINI_CLI PAO_DISABLE,$(if $($(v)),-e $(v)))
PHP_EXEC  := $(DOCKER_COMPOSE) exec --user application -T $(AGENT_ENV) -w $(PHP_WORKDIR) $(PHP_SERVICE)
PHP_COMPOSER   := $(PHP_EXEC) composer
PHP_ARTISAN    := $(PHP_EXEC) php artisan

# Localization: target locales for `make update-translation`, and the gitignored
# file holding the Anthropic key, a single `ANTHROPIC_API_KEY=...` line. It lives
# in the main checkout, which is also where a worktree's Compose project runs.
LOCALES   ?= fr es el zh_TW zh_CN
ENV_LOCAL ?= $(if $(COMPOSE_ROOT),$(COMPOSE_ROOT),.)/.env.local

# --locale is an array option and the JSON command does not split on commas: a
# comma-joined value is taken as one locale name and writes lang/fr,es,el.json.
LOCALE_FLAGS := $(foreach locale,$(LOCALES),--locale=$(locale))
NPM_EXEC       := npm

##@ Help

help: ## Display this help message
	@echo "$(GREEN)Available commands:$(NC)"
	@awk 'BEGIN {FS = ":.*##"; printf "\n"} /^[a-zA-Z_-]+:.*?##/ { printf "  $(YELLOW)%-15s$(NC) %s\n", $$1, $$2 } /^##@/ { printf "\n$(GREEN)%s$(NC)\n", substr($$0, 5) } ' $(MAKEFILE_LIST)

##@ Development

install: ## Install dependencies (composer + npm)
	$(PHP_COMPOSER) install
	$(PHP_ARTISAN) boost:update --no-discover --no-interaction
	$(NPM_EXEC) ci

setup: start install
	$(DOCKER_COMPOSE) restart app
	$(MAKE) build migrate db-seed
	$(DOCKER_COMPOSE) restart worker

start: ## Start development server (all services: php, queue, mysql, postgres)
	$(DOCKER_COMPOSE) up -d

migrate:
	$(PHP_ARTISAN) migrate

logs:
	$(DOCKER_COMPOSE) logs -f php

create-bucket: ## Create S3 bucket in rustfs (usage: make create-bucket BUCKET=my-bucket)
	docker run --rm --network=$$(docker network ls --filter name=databasement -q | head -1) \
		-e AWS_ACCESS_KEY_ID=rustfsadmin \
		-e AWS_SECRET_ACCESS_KEY=rustfsadmin \
		amazon/aws-cli \
		--endpoint-url=http://rustfs:9000 \
		s3 mb s3://$(or $(BUCKET),test-bucket) 2>/dev/null || true
##@ Testing

test: ## Run all tests in parallel (default)
	$(PHP_ARTISAN) test --parallel

test-filter: ## Run tests with filter (usage: make test-filter FILTER=DatabaseServer)
	$(PHP_ARTISAN) test --parallel  --filter="$(FILTER)"

test-coverage: ## Run tests with coverage
	$(PHP_ARTISAN) test --parallel --coverage

test-coverage-filter: ## Run tests with coverage and filter (usage: make test-coverage-filter FILTER=FailureNotification)
	$(PHP_ARTISAN) test --parallel --coverage --filter="$(FILTER)"

test-sequential: ## Run all tests sequentially (for debugging)
	$(PHP_ARTISAN) test

# --- Test Impact Analysis (Pest 5) -------------------------------------------
# TIA replays cached results and re-runs only the tests affected by your working
# tree, turning a ~110s suite into a ~2s replay.
#
# DELIBERATELY NOT wired into `make test`, the pre-commit hook, or CI. On this
# codebase the recorded graph only carries test edges for 177 of 291 app/ files;
# app/Livewire is almost entirely missing (2 of 70 files have edges), so edits
# there replay green instead of running the tests that cover them. Verified by
# deleting an authorize() call from a Livewire component: TIA reported 1487
# passed while the full suite failed. Treat TIA output as a hint, never a gate.
#
# `php artisan test` rejects --tia (Collision does not forward the option), so
# these targets invoke vendor/bin/pest directly.
test-tia: ## Fast inner-loop replay via Test Impact Analysis (NOT a substitute for `make test`)
	$(PHP_EXEC) vendor/bin/pest --parallel --tia

test-tia-baseline: ## (Re)record the TIA baseline -- --coverage is required, without it no app/ edges are recorded
	$(PHP_EXEC) vendor/bin/pest --parallel --tia --coverage --fresh

##@ Code Quality

lint-check: ## Check code style with Laravel Pint
	$(PHP_EXEC) vendor/bin/pint --test

lint-fix: ## Fix code style with Laravel Pint
	$(PHP_EXEC) vendor/bin/pint

lint: lint-fix ## Alias for lint-fix

ide-helper: ## Regenerate _ide_helper_models.php (gitignored; consumed by PHPStan via scanFiles)
	$(PHP_EXEC) php artisan ide-helper:models --write-mixin --no-interaction

phpstan: ## Run PHPStan static analysis
	$(PHP_EXEC) vendor/bin/phpstan analyse --memory-limit=1G

pre-commit: lint-fix ide-helper phpstan test ## Run all pre-commit checks (lint, ide-helper, phpstan, tests)

##@ Assets

build: ## Build production assets
	$(NPM_EXEC) run build

dev-assets: ## Start Vite dev server only
	$(NPM_EXEC) run dev

##@ Documentation

docs: ## Start documentation dev server (Docusaurus)
	cd docs && $(NPM_EXEC) install && $(NPM_EXEC) run start

docs-build: ## Build documentation for production (Docusaurus)
	cd docs && $(NPM_EXEC) install && $(NPM_EXEC) run build

##@ Localization

# Extract -> prune -> translate -> normalise -> report. Only the translate step
# calls the API, and it only sends the keys a locale is missing, so re-running
# after a failure costs nothing for what already landed. Sourcing .env.local with
# `set -a` exports the key into that one shell, and `-e ANTHROPIC_API_KEY` (name,
# no value) forwards it into the container without it reaching a command line.
update-translation: ## Update lang/*.json from the code, translating new strings with AI
	@test -f $(ENV_LOCAL) || { echo "$(YELLOW)Missing $(ENV_LOCAL) with ANTHROPIC_API_KEY (see the Makefile comment)$(NC)"; exit 1; }
	$(PHP_ARTISAN) translatable:export en
	$(PHP_ARTISAN) translations:sync
	set -a; . $(ENV_LOCAL); set +a; $(DOCKER_COMPOSE) exec -e ANTHROPIC_API_KEY \
		--user application -T -w $(PHP_WORKDIR) $(PHP_SERVICE) \
		php artisan ai-translator:translate-json --source=en $(LOCALE_FLAGS) --non-interactive
	$(PHP_ARTISAN) translations:sync
	$(PHP_ARTISAN) translations:sync --check

check-translation: ## Report locales that are out of sync with the code (no API calls)
	$(PHP_ARTISAN) translatable:export en
	$(PHP_ARTISAN) translations:sync --check

##@ Database

migrate-fresh: ## Drop all tables and re-migrate
	$(PHP_ARTISAN) migrate:fresh

migrate-fresh-seed: ## Drop all tables, re-migrate and seed
	$(PHP_ARTISAN) migrate:fresh --seed

db-seed: ## Run database seeders
	$(PHP_ARTISAN) db:seed

import-db: ## Import a gzipped SQL dump into local MySQL (usage: make import-db FILE=/path/to/dump.sql.gz)
	@if [ -z "$(FILE)" ]; then \
		echo "$(YELLOW)Usage: make import-db FILE=/path/to/dump.sql.gz$(NC)"; \
		exit 1; \
	fi
	@if [ ! -f "$(FILE)" ]; then \
		echo "$(YELLOW)Error: File '$(FILE)' not found$(NC)"; \
		exit 1; \
	fi
	@echo "$(GREEN)Dropping and recreating 'databasement' database...$(NC)"
	$(DOCKER_COMPOSE) exec -T mysql mysql -uroot -proot -e "DROP DATABASE IF EXISTS databasement; CREATE DATABASE databasement;"
	@echo "$(GREEN)Importing $(FILE)...$(NC)"
	gunzip -c "$(FILE)" | $(DOCKER_COMPOSE) exec -T mysql mysql -uroot -proot databasement
	@echo "$(GREEN)Import complete!$(NC)"

##@ Maintenance

clean: ## Clear all caches
	$(PHP_ARTISAN) cache:clear
	$(PHP_ARTISAN) config:clear
	$(PHP_ARTISAN) route:clear
	$(PHP_ARTISAN) view:clear

optimize: ## Optimize the application for production
	$(PHP_ARTISAN) config:cache
	$(PHP_ARTISAN) route:cache
	$(PHP_ARTISAN) view:cache

##@ Release

release: ## Create a new release (usage: make release VERSION=1.0.1)
	@if [ -z "$(VERSION)" ]; then \
		echo "$(YELLOW)Usage: make release VERSION=1.0.1$(NC)"; \
		exit 1; \
	fi
	@for skill in changelog release-issues; do \
		if [ ! -f .claude/skills/$$skill/SKILL.md ]; then \
			echo "$(YELLOW)Error: the /$$skill skill is not installed in .claude/skills. Run 'make install' (or php artisan boost:update) first.$(NC)"; \
			exit 1; \
		fi; \
	done
	@if [ "$$(git branch --show-current)" != "main" ]; then \
		echo "$(YELLOW)Error: You must be on the main branch to create a release.$(NC)"; \
		exit 1; \
	fi
	@git fetch origin main --quiet
	@if [ "$$(git rev-parse HEAD)" != "$$(git rev-parse origin/main)" ]; then \
		echo "$(YELLOW)Error: Local main is not up to date with origin/main. Run 'git pull' first.$(NC)"; \
		exit 1; \
	fi
	@if [ -n "$$(git status --porcelain)" ]; then \
		echo "$(YELLOW)Error: Working directory is not clean. Commit or stash changes first.$(NC)"; \
		exit 1; \
	fi
	@version=$(VERSION); breaking=$$(git log HEAD --not --tags --no-merges --format='%h %s' | grep -E '^[0-9a-f]+ [a-z]+\(agent\)!:'); \
	if [ -n "$$breaking" ] && [ "$${version##*.}" != "0" ]; then \
		echo "$(YELLOW)Error: these commits break agents, so $(VERSION) must open a new minor (x.y.0), not a patch:$(NC)"; \
		echo "$$breaking"; \
		exit 1; \
	fi
	@if ! grep -q '^- `$(VERSION)`' CHANGELOG.md; then \
		echo "$(GREEN)No $(VERSION) entries in CHANGELOG.md, writing them with Claude Code (/changelog $(VERSION))...$(NC)"; \
		claude -p "/changelog $(VERSION)" \
			--allowedTools "Bash(git *),Bash(gh *),Bash(date *),Read,Edit,Write" || exit 1; \
	fi
	@if ! grep -q '^- `$(VERSION)`' CHANGELOG.md; then \
		echo "$(YELLOW)Error: CHANGELOG.md still has no $(VERSION) entries. Run /changelog $(VERSION) in Claude Code and check its output.$(NC)"; \
		exit 1; \
	fi
	@if [ -n "$$(git status --porcelain)" ]; then \
		echo "$(YELLOW)Error: the changelog entry was written but not committed. Commit and push it, then rerun.$(NC)"; \
		exit 1; \
	fi
	@git fetch origin main --quiet
	@if [ "$$(git rev-parse HEAD)" != "$$(git rev-parse origin/main)" ]; then \
		echo "$(YELLOW)Error: the changelog commit is not on origin/main yet. Push it, then rerun.$(NC)"; \
		exit 1; \
	fi
	@echo "$(GREEN)Creating release v$(VERSION)...$(NC)"
	git tag v$(VERSION)
	git push origin v$(VERSION)
	@echo "$(GREEN)Release v$(VERSION) created! Workflows will build Docker images, Helm chart, and GitHub Release.$(NC)"
	@echo "$(GREEN)Commenting on the issues v$(VERSION) resolves (/release-issues $(VERSION))...$(NC)"
	@claude -p "/release-issues $(VERSION)" \
		--allowedTools "Bash(git *),Bash(gh *),Read" \
		|| echo "$(YELLOW)Warning: the issue comments failed. The release is out; rerun /release-issues $(VERSION) in Claude Code.$(NC)"