Ctrl K
ComunidadGuías…

Makefile

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

Archivo original ↗Preguntar a KiBot





.DEFAULT_GOAL := help

HELP_delta-preflight := Check local checkout stack prerequisites without creating resources
HELP_delta-prepare := Install/build/migrate this checkout in an isolated local pod
HELP_delta-up := Start this checkout's full local dev stack in the background
HELP_delta-dev := Own the stack in the foreground; stop it when the terminal ends
HELP_delta-stop := Stop this checkout's stack and preserve all state
HELP_delta-status := Show this checkout's containers
HELP_delta-urls := Print this checkout's service URLs
HELP_delta-logs := Follow container logs (SERVICE=dev,wordpress,proxy,db,pg)
HELP_delta-preview := Rebuild the WordPress preview bridge after source changes
HELP_delta-check := Check all live endpoints and rendered WordPress local configuration
HELP_delta-tls := Configure this checkout's proxy with the existing host mkcert CA
HELP_delta-remove := Destroy this checkout's stack (requires exact CONFIRM identity)

.PHONY: delta-dev delta-preflight delta-prepare delta-up delta-start delta-stop delta-status delta-urls delta-logs delta-check delta-remove delta-identity delta-preview delta-syntax delta-tls
delta-dev delta-preflight delta-prepare delta-up delta-start delta-stop delta-status delta-urls delta-logs delta-check delta-remove delta-identity delta-preview delta-tls:
	@DEV_IMAGE="$(DEV_IMAGE)" SERVICE="$(SERVICE)" CONFIRM="$(CONFIRM)" bash bin/delta-stack $(@:delta-%=%)

delta-syntax:
	@for script in bin/delta-stack bin/prepare-server-deps infra/local/delta-dev.sh infra/local/delta-processes.sh infra/local/delta-processes.test.sh infra/local/worktree-setup.sh .agents/prepare; do bash -n "$$script" || exit; done
	@python3 -c 'import ast, pathlib; [ast.parse(pathlib.Path(p).read_text()) for p in ("infra/local/delta-lifecycle.py", "infra/local/delta-lifecycle.test.py", "infra/local/delta-cleanup.test.py")]'

.PHONY: delta-test-processes
delta-test-processes:
	bash infra/local/delta-processes.test.sh

.PHONY: delta-test-lifecycle
delta-test-lifecycle:
	python3 infra/local/delta-lifecycle.test.py

.PHONY: delta-test-cleanup
delta-test-cleanup:
	python3 infra/local/delta-cleanup.test.py


CYAN := \033[36m
GREEN := \033[32m
YELLOW := \033[33m
RESET := \033[0m




DEVPOD_USER ?= root

define require_devpod_server
	@if [ -z "$(DEVPOD_SERVER)" ]; then \
		printf "$(YELLOW)Error: DEVPOD_SERVER is not set. Run via: op run --env-file=.env -- make $(1)$(RESET)\n"; \
		exit 1; \
	fi
endef



define require_branch
	@if [ -z "$(BRANCH)" ]; then \
		printf "$(YELLOW)Error: BRANCH is required. Usage: make $(1) BRANCH=feature-name$(RESET)\n"; \
		exit 1; \
	fi
endef




define resolve_branch
	$(eval _BRANCH_WAS := $(BRANCH))
	$(eval BRANCH := $(if $(BRANCH),$(BRANCH),$(shell git branch --show-current)))
	@if [ -z "$(BRANCH)" ]; then \
		printf "$(YELLOW)Error: Could not detect branch. Pass it explicitly: make $(1) BRANCH=feature-name$(RESET)\n"; \
		exit 1; \
	fi
	@if [ -z "$(_BRANCH_WAS)" ]; then \
		printf "$(CYAN)Auto-detected branch: $(BRANCH)$(RESET)\n"; \
	fi
endef



define run_e2e
	@test -f test/e2e/.env || { printf "$(YELLOW)Error: test/e2e/.env not found. Copy test/e2e/.env.example and fill in values.$(RESET)\n"; exit 1; }
	@set -eu; \
	workos_api_key_set="$${WORKOS_API_KEY+x}"; workos_api_key="$${WORKOS_API_KEY-}"; \
	workos_client_id_set="$${WORKOS_CLIENT_ID+x}"; workos_client_id="$${WORKOS_CLIENT_ID-}"; \
	set -a; . test/e2e/.env; set +a; \
	if [ -n "$$workos_api_key_set" ]; then export WORKOS_API_KEY="$$workos_api_key"; fi; \
	if [ -n "$$workos_client_id_set" ]; then export WORKOS_CLIENT_ID="$$workos_client_id"; fi; \
	CA_CERT="$${NODE_EXTRA_CA_CERTS:-$$(mkcert -CAROOT)/rootCA.pem}"; \
	test -r "$$CA_CERT" || { printf "Error: test CA not readable: %s. Run make ssl-setup.\n" "$$CA_CERT"; exit 1; }; \
	export NODE_EXTRA_CA_CERTS="$$(realpath "$$CA_CERT")"; \
	cd test/e2e && npx vitest run $(1)
endef

.PHONY: help

HELP_SECTIONS := DEVELOPMENT BUILD SSL WT INFRA REL E2E UTIL

HELP_DEVELOPMENT_TITLE := Development
HELP_DEVELOPMENT_TARGETS := dev dev-client dev-server dev-nextjs dev-nextjs-prebuilt dev-marketing delta-preflight delta-prepare delta-dev delta-up delta-stop delta-status delta-urls delta-logs delta-preview delta-check delta-tls delta-remove
HELP_dev := Start all core services (client + server + nextjs)
HELP_dev-client := Start development server for client app
HELP_dev-server := Start development server for server app
HELP_dev-nextjs := Start development server for Next.js test site
HELP_dev-nextjs-prebuilt := Start Next.js test site with prebuilt integration
HELP_dev-marketing := Start development server for marketing site

HELP_BUILD_TITLE := Build & Quality
HELP_BUILD_TARGETS := install prepare-deps hooks-install setup-elixir-tools verify-toolchain-pins build rescript-watch rescript-build rescript-format reanalyze check-source-comments clean
HELP_install := Install dependencies
HELP_prepare-deps := Fetch locked JS and server dependencies without compiling applications
HELP_hooks-install := Install git pre-commit hooks via Lefthook
HELP_setup-elixir-tools := Install Hex/Rebar for the active mise Elixir
HELP_verify-toolchain-pins := Verify Docker Elixir image matches mise.toml
HELP_build := Build ReScript project
HELP_rescript-watch := Watch and rebuild ReScript on changes
HELP_rescript-build := Build ReScript project (one-shot)
HELP_rescript-format := Format ReScript source
HELP_reanalyze := Run ReScript dead code analysis
HELP_check-source-comments := Test scanner and check repository source comments
HELP_clean := Clean ReScript build artifacts

HELP_SSL_TITLE := SSL & Networking
HELP_SSL_TARGETS := ssl-setup tunnel
HELP_ssl-setup := Setup local SSL certificates using mkcert
HELP_tunnel := Start SSH tunnel to DevPod server (fallback if dnsmasq not configured)

HELP_WT_TITLE := Worktrees
HELP_WT_TARGETS := work wt wt-new wt-dev wt-stop wt-start wt-sh wt-rm wt-urls wt-logs
HELP_work := Set up worktree from GitHub issue or PR (REF=<number|url>)
HELP_wt := Dashboard — shows all worktrees, pod status, URLs, and actions
HELP_wt-new := Create containerized worktree (BRANCH=...)
HELP_wt-dev := Start dev servers in container (BRANCH=...)
HELP_wt-stop := Pause worktree pod, preserve volumes (BRANCH=...)
HELP_wt-start := Resume a paused worktree pod (BRANCH=...)
HELP_wt-sh := Shell into dev container (BRANCH=...)
HELP_wt-rm := Full cleanup: pod + volumes + worktree (BRANCH=...)
HELP_wt-urls := Show service URLs for a worktree (BRANCH=...)
HELP_wt-logs := Tail dev container logs (BRANCH=...)

HELP_INFRA_TITLE := Infrastructure
HELP_INFRA_TARGETS := infra-up infra-down infra-build
HELP_infra-up := One-time setup: dev image, Caddy, dnsmasq
HELP_infra-down := Tear down all pods, volumes, and Caddy
HELP_infra-build := Rebuild the frontman-dev container image

HELP_REL_TITLE := Release
HELP_REL_TARGETS := publish publish-astro publish-vite publish-nextjs publish-react-statestore publish-swarm-ai release package-wordpress-plugin package-openai-wordpress publish-wordpress-plugin-svn build-wordpress-dependencies wordpress-composer-lock test-wordpress-core-tools test-wordpress-sentry test-wordpress-runtime
HELP_publish := Publish all npm packages (pass OTP=<code> for 2FA)
HELP_publish-astro := Publish @frontman-ai/astro to npm (pass OTP=<code> for 2FA)
HELP_publish-vite := Publish @frontman-ai/vite to npm (pass OTP=<code> for 2FA)
HELP_publish-nextjs := Publish @frontman-ai/nextjs to npm (pass OTP=<code> for 2FA)
HELP_publish-react-statestore := Publish @frontman-ai/react-statestore to npm (pass OTP=<code> for 2FA)
HELP_publish-swarm-ai := Publish swarm_ai to Hex (dry run by default, HEX_PUBLISH=1 for real)
HELP_release := Create a release PR from pending changesets
HELP_package-wordpress-plugin := Build WordPress ZIP and WordPress.org bundle
HELP_package-openai-wordpress := Build the OpenAI WordPress candidate for VERSION (does not submit)
HELP_publish-wordpress-plugin-svn := Publish WordPress.org bundle to SVN (requires WORDPRESS_ORG_* env vars)
HELP_test-wordpress-core-tools := Run PHP tests (select basenames with WORDPRESS_TESTS="ToolRegistryTest ElementorToolsTest")
HELP_test-wordpress-runtime := Run plugin integration tests in WordPress containers
HELP_build-wordpress-dependencies := Bundle the locked PHP SDK (Docker, or CONTAINER_RUNTIME=podman)
HELP_wordpress-composer-lock := Update PHP dependencies while preserving PHP 7.4 compatibility
HELP_test-wordpress-sentry := Test PHP diagnostics and SDK coexistence (PHP_VERSION=7.4 or 8.4)

HELP_E2E_TITLE := E2E Tests
HELP_E2E_TARGETS := e2e e2e-nextjs e2e-nextjs-compat e2e-astro e2e-vite e2e-vue-vite e2e-oauth-start
HELP_e2e := Run all e2e tests (loads secrets from test/e2e/.env)
HELP_e2e-nextjs := Run Next.js e2e test
HELP_e2e-nextjs-compat := Run packed Next.js dev/build compatibility check (NEXT_VERSION=16)
HELP_e2e-astro := Run Astro e2e test
HELP_e2e-vite := Run Vite e2e test
HELP_e2e-vue-vite := Run Vue + Vite e2e test
HELP_e2e-oauth-start := Run OAuth start smoke e2e test

HELP_UTIL_TITLE := Utilities
HELP_UTIL_TARGETS := kill-all-processes pull-webapi test-webapi debug-task push
HELP_kill-all-processes := Kill all running make dev processes
HELP_pull-webapi := Pull latest experimental-rescript-webapi subtree
HELP_test-webapi := Run vendored WebAPI runtime tests
HELP_debug-task := Debug task interactions (ARGS="list" or ARGS="show ...")
HELP_push := Git push current branch

help:
	@printf "$(CYAN)Frontman Monorepo$(RESET)\n"
	@$(foreach section,$(HELP_SECTIONS),printf "\n$(CYAN)$(HELP_$(section)_TITLE):$(RESET)\n"; $(foreach target,$(HELP_$(section)_TARGETS),printf "  $(GREEN)%-25s$(RESET)  %s\n" "$(target)" '$(HELP_$(target))';))
	@echo ""




STRIPE_WEBHOOK_EVENTS := checkout.session.completed,customer.subscription.created,customer.subscription.updated,customer.subscription.deleted,customer.subscription.paused,customer.subscription.resumed
STRIPE_WEBHOOK_FORWARD_URL ?= https://localhost:4000/api/stripe/webhook
STRIPE_WEBHOOK_ENV_FILE := apps/frontman_server/envs/.dev.stripe-webhook.env

.PHONY: stripe-webhooks wait-stripe-webhook-secret
stripe-webhooks:
	STRIPE_WEBHOOK_FORWARD_URL="$(STRIPE_WEBHOOK_FORWARD_URL)" STRIPE_WEBHOOK_EVENTS="$(STRIPE_WEBHOOK_EVENTS)" bash ./bin/stripe-webhooks

wait-stripe-webhook-secret:
	@printf "$(YELLOW)Waiting for generated Stripe webhook secret...$(RESET)\n"
	@while ! grep -q '^STRIPE_WEBHOOK_SECRET=whsec_' "$(STRIPE_WEBHOOK_ENV_FILE)" 2>/dev/null; do sleep 1; done

.PHONY: dev dev-client dev-server dev-nextjs dev-nextjs-prebuilt dev-marketing

dev:
	@printf "$(YELLOW)Starting all services via mprocs...$(RESET)\n"
	mprocs --config mprocs.yml

dev-client:
	@printf "$(YELLOW)Starting client dev server...$(RESET)\n"
	cd libs/client && $(MAKE) dev

dev-server:
	@printf "$(YELLOW)Starting server dev server...$(RESET)\n"
	cd apps/frontman_server && $(MAKE) dev

dev-nextjs:
	@printf "$(YELLOW)Starting Next.js dev server...$(RESET)\n"
	cd test/sites/blog-starter && $(MAKE) dev

dev-nextjs-prebuilt:
	@printf "$(YELLOW)Starting Next.js dev server...$(RESET)\n"
	cd test/sites/blog-starter && $(MAKE) dev-prebuilt

dev-marketing:
	@printf "$(YELLOW)Waiting for server on localhost:4000...$(RESET)\n"
	@bash -c 'while ! (: > /dev/tcp/localhost/4000) 2>/dev/null; do sleep 1; done'
	@printf "$(YELLOW)Starting marketing dev server...$(RESET)\n"
	cd apps/marketing && $(MAKE) dev




.PHONY: install build rescript-watch rescript-build rescript-format reanalyze clean hooks-install setup-elixir-tools verify-toolchain-pins check-source-comments

.PHONY: security-dependencies
security-dependencies:
	yarn up -R next sharp js-yaml svgo browserslist postcss-selector-parser qs vitest @vitest/browser-playwright @vitest/coverage-v8 mermaid

install:
	@printf "$(YELLOW)Installing dependencies...$(RESET)\n"
	yarn install
	@$(MAKE) hooks-install

.PHONY: prepare-deps prepare-server-deps
prepare-deps: prepare-server-deps
	@MISE_YES=false MISE_AUTO_INSTALL=false YARN_ENABLE_GLOBAL_CACHE=true mise exec node yarn -- yarn install --immutable </dev/null || { \
		printf 'JS dependency preparation failed. Read the error above. For toolchain errors, run mise trust and mise install node yarn, then retry make prepare-deps.\n' >&2; exit 1; \
	}

prepare-server-deps:
	@command -v mise >/dev/null || { printf 'Install mise and the tools pinned in mise.toml first.\n' >&2; exit 1; }
	@MISE_YES=false MISE_AUTO_INSTALL=false mise exec elixir erlang -- bash bin/prepare-server-deps </dev/null || { \
		printf 'Server dependency preparation failed. For toolchain errors, run mise trust and mise install elixir erlang explicitly, then retry make prepare-deps.\n' >&2; exit 1; \
	}

hooks-install:
	@printf "$(YELLOW)Installing git hooks...$(RESET)\n"
	@if command -v lefthook &> /dev/null; then \
		lefthook install; \
		printf "$(GREEN)Git hooks installed.$(RESET)\n"; \
	else \
		printf "$(YELLOW)lefthook not found — run 'mise install' first.$(RESET)\n"; \
	fi

setup-elixir-tools:
	@printf "$(YELLOW)Installing Hex/Rebar for mise Elixir...$(RESET)\n"
	mise exec -- mix local.hex --force
	@tmp=$$(mktemp -t rebar3.XXXXXX); \
	curl -fsSL "https://s3.amazonaws.com/rebar3/rebar3" -o "$$tmp"; \
	chmod +x "$$tmp"; \
	mise exec -- mix local.rebar rebar3 "$$tmp" --force; \
	rm -f "$$tmp"
	@printf "$(GREEN)Hex/Rebar ready.$(RESET)\n"

verify-toolchain-pins:
	@elixir_tool=$$(awk -F'"' '/^elixir *=/ {print $$2}' mise.toml); \
	erlang_tool=$$(awk -F'"' '/^erlang *=/ {print $$2}' mise.toml); \
	docker_image=$$(awk '/^FROM hexpm\/elixir:/ {print $$2; exit}' apps/frontman_server/Dockerfile); \
	elixir_version="$${elixir_tool%%-otp-*}"; \
	expected="hexpm/elixir:$${elixir_version}-erlang-$${erlang_tool}"; \
	if [ -z "$$elixir_tool" ] || [ -z "$$erlang_tool" ] || [ -z "$$docker_image" ]; then \
		printf "$(YELLOW)Could not read mise.toml or Dockerfile toolchain pins.$(RESET)\n"; \
		exit 1; \
	fi; \
	case "$$docker_image" in \
		$$expected*) printf "$(GREEN)Toolchain pins match: $$docker_image$(RESET)\n" ;; \
		*) printf "$(YELLOW)Toolchain pin mismatch: expected Docker image prefix '$$expected', got '$$docker_image'.$(RESET)\n"; exit 1 ;; \
	esac

build:
	@printf "$(YELLOW)Building ReScript project...$(RESET)\n"
	yarn rescript build $(RESCRIPT_FLAGS)

rescript-watch:
	@printf "$(YELLOW)Starting ReScript watch mode...$(RESET)\n"
	yarn rescript watch

rescript-build:
	@printf "$(YELLOW)Starting ReScript build...$(RESET)\n"
	yarn rescript build

rescript-format:
	git ls-files -z -- '*.res' '*.resi' ':(exclude)libs/experimental-rescript-webapi/**' | xargs -0 -r sh -c 'for file do if [ -f "$$file" ]; then printf "%s\0" "$$file"; fi; done' sh | xargs -0 -r yarn rescript format

reanalyze:
	@printf "$(YELLOW)Running ReScript dead code analysis...$(RESET)\n"
	yarn rescript-tools reanalyze

check-source-comments:
	node --test test/no-comments/no-comments.test.mjs
	node scripts/no-comments.mjs --check

clean:
	@printf "$(YELLOW)Cleaning build artifacts...$(RESET)\n"
	yarn rescript clean





.PHONY: e2e e2e-nextjs e2e-nextjs-compat e2e-astro e2e-vite e2e-vue-vite e2e-oauth-start

e2e:
	@printf "$(YELLOW)Running all e2e tests...$(RESET)\n"
	$(call run_e2e)

e2e-nextjs:
	@printf "$(YELLOW)Running Next.js e2e test...$(RESET)\n"
	$(call run_e2e,tests/nextjs.test.ts)

e2e-nextjs-compat:
	@printf "$(YELLOW)Running packed Next.js compatibility check...$(RESET)\n"
	yarn rescript
	yarn workspace @frontman-ai/nextjs build
	NEXT_VERSION=$${NEXT_VERSION:-16} bash scripts/ci/nextjs-compat.sh

e2e-astro:
	@printf "$(YELLOW)Running Astro e2e test...$(RESET)\n"
	$(call run_e2e,tests/astro.test.ts)

e2e-vite:
	@printf "$(YELLOW)Running Vite e2e test...$(RESET)\n"
	$(call run_e2e,tests/vite.test.ts)

e2e-vue-vite:
	@printf "$(YELLOW)Running Vue + Vite e2e test...$(RESET)\n"
	$(call run_e2e,tests/vue-vite.test.ts)

e2e-oauth-start:
	@printf "$(YELLOW)Running OAuth start smoke e2e test...$(RESET)\n"
	$(call run_e2e,tests/oauth-start.test.ts)





.PHONY: ssl-setup tunnel

ssl-setup:
	@printf "$(YELLOW)Setting up SSL certificates...$(RESET)\n"
	@mkdir -p .certs
	mkcert -install
	cd .certs && mkcert frontman.local localhost 127.0.0.1 ::1
	mv .certs/frontman.local+3.pem .certs/frontman.local.pem
	mv .certs/frontman.local+3-key.pem .certs/frontman.local-key.pem
	sudo sh -c 'grep -q frontman.local /etc/hosts || echo "127.0.0.1 frontman.local" >> /etc/hosts'

tunnel:
	$(call require_devpod_server,tunnel)
	@printf "$(YELLOW)Starting SSH tunnel to $(DEVPOD_USER)@$(DEVPOD_SERVER)$(RESET)\n"
	@echo "  Local :8080 → Remote :80 (HTTP)"
	@echo "  Local :8443 → Remote :443 (HTTPS)"
	@echo ""
	@echo "NOTE: With dnsmasq configured, you don't need this tunnel."
	@echo "Press Ctrl+C to stop the tunnel"
	ssh -L 8080:localhost:80 -L 8443:localhost:443 $(DEVPOD_USER)@$(DEVPOD_SERVER) -N






















CADDY_CONTAINER := frontman-caddy
DEV_IMAGE := frontman-dev:latest


export MD5CMD := $(shell if command -v md5sum >/dev/null 2>&1; then echo 'md5sum | cut -c1-4'; else echo 'md5 | cut -c1-4'; fi)

.PHONY: wt wt-new wt-dev wt-stop wt-start wt-sh wt-rm wt-urls wt-logs work

work:
	@if [ -z "$(REF)" ]; then \
		printf "$(YELLOW)Usage: make work REF=<issue-number|issue-url|pr-url>$(RESET)\n"; \
		exit 1; \
	fi
	@REF="$(REF)" DEV_IMAGE=$(DEV_IMAGE) bash ./bin/work

wt:
	@bash ./bin/wt-dashboard

wt-new:
	$(call resolve_branch,wt-new)
	@BRANCH="$(BRANCH)" WORKTREE_BASE_BRANCH="$(WORKTREE_BASE_BRANCH)" DEV_IMAGE=$(DEV_IMAGE) \
		bash ./bin/wt-pod-create

wt-dev:
	$(call resolve_branch,wt-dev)
	@BRANCH="$(BRANCH)" CADDY_CONTAINER=$(CADDY_CONTAINER) \
		bash ./bin/wt-pod-dev

wt-stop:
	$(call resolve_branch,wt-stop)
	@POD=$$(BRANCH="$(BRANCH)" bash ./bin/wt-resolve pod) || exit 1; \
	podman pod stop "$$POD"; \
	bash ./infra/local/caddy-regen.sh; \
	printf "$(GREEN)Stopped. Resume with: make wt-start BRANCH=$(BRANCH)$(RESET)\n"

wt-start:
	$(call resolve_branch,wt-start)
	@POD=$$(BRANCH="$(BRANCH)" bash ./bin/wt-resolve pod) || exit 1; \
	podman pod start "$$POD"; \
	bash ./infra/local/caddy-regen.sh; \
	printf "$(GREEN)Started. Run: make wt-dev BRANCH=$(BRANCH)$(RESET)\n"

wt-sh:
	$(call resolve_branch,wt-sh)
	@CONTAINER=$$(BRANCH="$(BRANCH)" bash ./bin/wt-resolve container) || exit 1; \
	podman exec -it -w /workspaces/frontman "$$CONTAINER" bash

wt-rm:
	$(call resolve_branch,wt-rm)
	@BRANCH="$(BRANCH)" bash ./bin/wt-pod-remove

wt-urls:
	$(call resolve_branch,wt-urls)
	@HASH=$$(BRANCH="$(BRANCH)" bash ./bin/wt-resolve hash); \
	echo ""; \
	printf "$(CYAN)$(BRANCH) ($$HASH)$(RESET)\n"; \
	echo ""; \
	printf "  $(GREEN)Phoenix$(RESET)     https://$$HASH.api.frontman.local\n"; \
	printf "  $(GREEN)Vite$(RESET)        https://$$HASH.vite.frontman.local\n"; \
	printf "  $(GREEN)Next.js$(RESET)     https://$$HASH.nextjs.frontman.local/frontman\n"; \
	printf "  $(GREEN)Marketing$(RESET)   https://$$HASH.marketing.frontman.local\n"; \
	echo ""

wt-logs:
	$(call resolve_branch,wt-logs)
	@CONTAINER=$$(BRANCH="$(BRANCH)" bash ./bin/wt-resolve container) || exit 1; \
	podman logs -f "$$CONTAINER"





.PHONY: infra-up infra-down infra-build

infra-up:
	@printf "$(CYAN)Setting up containerized worktree infrastructure...$(RESET)\n"
	@echo ""
	@printf "$(YELLOW)Building dev image: $(DEV_IMAGE)$(RESET)\n"
	@podman build -t $(DEV_IMAGE) -f .devcontainer/Dockerfile .devcontainer/
	@echo ""
	@if ! podman container inspect $(CADDY_CONTAINER) &>/dev/null; then \
		printf "$(YELLOW)Starting Caddy reverse proxy (host network)...$(RESET)\n"; \
		mkdir -p infra/local; \
		printf ':9999 {\n    respond "No worktree pods running" 503\n}\n' > infra/local/Caddyfile; \
		podman run -d \
			--name $(CADDY_CONTAINER) \
			--network host \
			-v "$$(pwd)/infra/local/Caddyfile:/etc/caddy/Caddyfile:ro" \
			-v frontman-caddy-data:/data \
			-v frontman-caddy-config:/config \
			docker.io/library/caddy:2-alpine; \
	else \
		printf "$(GREEN)Caddy container already exists$(RESET)\n"; \
		podman start $(CADDY_CONTAINER) 2>/dev/null || true; \
	fi
	@echo ""
	@if command -v dnsmasq &>/dev/null && [ -f /etc/dnsmasq.d/frontman.conf ]; then \
		printf "$(GREEN)dnsmasq: configured$(RESET)\n"; \
	else \
		printf "$(YELLOW)dnsmasq: not configured — run: sudo ./infra/local/dnsmasq-setup.sh$(RESET)\n"; \
	fi
	@echo ""
	@printf "$(GREEN)Infrastructure ready!$(RESET)\n"

infra-down:
	@printf "$(YELLOW)Tearing down infrastructure...$(RESET)\n"
	@PODS=$$(podman pod ls --format '{{.Name}}' 2>/dev/null | grep '^worktree-' || true); \
	if [ -n "$$PODS" ]; then \
		for POD in $$PODS; do printf "  Removing $$POD...\n"; podman pod rm -f "$$POD" 2>/dev/null || true; done; \
	fi
	@VOLS=$$(podman volume ls --format '{{.Name}}' 2>/dev/null | grep '^worktree-' || true); \
	if [ -n "$$VOLS" ]; then echo "$$VOLS" | xargs podman volume rm -f 2>/dev/null || true; fi
	@podman rm -f $(CADDY_CONTAINER) 2>/dev/null || true
	@podman volume rm -f frontman-caddy-data frontman-caddy-config 2>/dev/null || true
	@printf "$(GREEN)Infrastructure torn down$(RESET)\n"
	@echo "Note: git worktrees and dnsmasq config are preserved"

infra-build:
	@podman build -t $(DEV_IMAGE) -f .devcontainer/Dockerfile .devcontainer/





.PHONY: worktree-create worktree-list worktree-remove worktree-clean \
        worktree-register worktree-registry



worktree-create:
	$(call require_branch,worktree-create)
	@WORKTREE_NAME=$$(echo "$(BRANCH)" | sed 's|^origin/||'); \
	WORKTREE_BASE_BRANCH=$${WORKTREE_BASE_BRANCH:-$(shell git branch --show-current)}; \
	mkdir -p .worktrees; \
	if git show-ref --verify --quiet "refs/heads/$$WORKTREE_NAME" || \
	   git show-ref --verify --quiet "refs/remotes/origin/$$WORKTREE_NAME" || \
	   git show-ref --verify --quiet "refs/remotes/$(BRANCH)"; then \
		git worktree add ".worktrees/$$WORKTREE_NAME" $(BRANCH); \
	else \
		if [ -n "$$WORKTREE_BASE_BRANCH" ]; then \
			git worktree add ".worktrees/$$WORKTREE_NAME" -b "$$WORKTREE_NAME" "$$WORKTREE_BASE_BRANCH"; \
		else \
			git worktree add ".worktrees/$$WORKTREE_NAME" -b "$$WORKTREE_NAME"; \
		fi; \
	fi; \
	printf "$(GREEN)Worktree created at: .worktrees/$$WORKTREE_NAME$(RESET)\n"

.PHONY: test-production-backup
test-production-backup:
	@for script in infra/production/backup-pg.sh infra/production/backup-pg.test.sh infra/production/build-and-deploy.sh; do bash -n "$$script" || exit; done
	bash infra/production/backup-pg.test.sh

worktree-list:
	@git worktree list

worktree-remove:
	$(call require_branch,worktree-remove)
	@if [ ! -d ".worktrees/$(BRANCH)" ]; then \
		printf "$(YELLOW)Error: Worktree '.worktrees/$(BRANCH)' does not exist$(RESET)\n"; exit 1; \
	fi
	@if git -C .worktrees/$(BRANCH) diff --quiet && git -C .worktrees/$(BRANCH) diff --cached --quiet; then \
		git worktree remove .worktrees/$(BRANCH); printf "$(GREEN)Worktree removed$(RESET)\n"; \
	else \
		printf "$(YELLOW)Error: Uncommitted changes. Force: git worktree remove --force .worktrees/$(BRANCH)$(RESET)\n"; exit 1; \
	fi

worktree-clean:
	@git worktree prune && printf "$(GREEN)Done$(RESET)\n"

worktree-register:
	$(call require_devpod_server,worktree-register)
	@if [ -z "$(BRANCH)" ] || [ -z "$(CONTAINER)" ]; then \
		printf "$(YELLOW)Error: BRANCH and CONTAINER required$(RESET)\n"; exit 1; \
	fi
	ssh $(DEVPOD_USER)@$(DEVPOD_SERVER) "register-worktree $(BRANCH) $(CONTAINER)"

worktree-registry:
	$(call require_devpod_server,worktree-registry)
	@ssh $(DEVPOD_USER)@$(DEVPOD_SERVER) "cat /etc/caddy/worktrees/registry.json 2>/dev/null | jq . || echo 'No worktrees registered'"




.PHONY: publish publish-astro publish-vite publish-nextjs publish-react-statestore publish-swarm-ai release package-wordpress-plugin package-openai-wordpress publish-wordpress-plugin-svn test-wordpress-core-tools

publish: publish-astro publish-vite publish-nextjs publish-react-statestore

publish-astro:
	cd libs/frontman-astro && $(MAKE) publish OTP=$(OTP)

publish-vite:
	cd libs/frontman-vite && $(MAKE) publish OTP=$(OTP)

publish-nextjs:
	cd libs/frontman-nextjs && $(MAKE) publish OTP=$(OTP)

publish-react-statestore:
	cd libs/react-statestore && $(MAKE) publish OTP=$(OTP)

publish-swarm-ai:
	cd apps/swarm_ai && $(MAKE) hex-publish HEX_PUBLISH=$(HEX_PUBLISH)

release:
	@printf "$(CYAN)Checking release prerequisites...$(RESET)\n"
	@git fetch origin main --quiet
	@LOCAL=$$(git rev-parse HEAD); \
	REMOTE=$$(git rev-parse origin/main); \
	if [ "$$LOCAL" != "$$REMOTE" ]; then \
		printf "$(YELLOW)Error: local HEAD is not up to date with origin/main$(RESET)\n"; \
		echo "Run 'git pull origin main' first"; \
		exit 1; \
	fi
	@CHANGESETS=$$(find .changeset -name '*.md' ! -name 'README.md' 2>/dev/null | wc -l); \
	if [ "$$CHANGESETS" -eq 0 ]; then \
		printf "$(YELLOW)Error: no pending changesets found$(RESET)\n"; \
		echo "Add changesets with 'yarn changeset' before releasing"; \
		exit 1; \
	fi; \
	printf "$(GREEN)Found $$CHANGESETS pending changeset(s)$(RESET)\n"
	@printf "$(CYAN)Validating changesets...$(RESET)\n"
	@yarn changeset status
	@printf "$(YELLOW)Triggering release workflow...$(RESET)\n"
	@gh workflow run release-pr.yml --ref main
	@printf "$(GREEN)Release workflow triggered.$(RESET)\n"
	@echo "Watch for the PR at: https://github.com/frontman-ai/frontman/pulls"

.PHONY: build-wordpress-dependencies wordpress-composer-lock test-wordpress-sentry build-wordpress-preview test-wordpress-preview

test-wordpress-preview: build-wordpress-preview
	@$(or $(CONTAINER_RUNTIME),docker) run --rm -v "$(CURDIR):/workspace:ro" -w /workspace docker.io/library/php:$(or $(PHP_VERSION),8.4)-cli php -d auto_prepend_file=libs/frontman-wordpress/tests/ErrorHandler.php libs/frontman-wordpress/tests/PreviewBridgeTest.php

build-wordpress-preview:
	$(MAKE) -C libs/frontman-preview-bridge build
	cp libs/frontman-preview-bridge/dist/bridge.js libs/frontman-preview-bridge/dist/bridge-licenses.txt libs/frontman-wordpress/assets/

build-wordpress-dependencies:
	@bash ./scripts/build-wordpress-dependencies.sh

wordpress-composer-lock:
	@bash ./scripts/build-wordpress-dependencies.sh --update-lock

test-wordpress-sentry: build-wordpress-dependencies
	@$(or $(CONTAINER_RUNTIME),docker) run --rm -v "$(CURDIR):/workspace:ro" -w /workspace docker.io/library/php:$(or $(PHP_VERSION),8.4)-cli sh -ec 'for order in --frontman-first --site-first; do php -d auto_prepend_file=libs/frontman-wordpress/tests/ErrorHandler.php libs/frontman-wordpress/tests/SentryTest.php "$$order"; done; php -d auto_prepend_file=libs/frontman-wordpress/tests/ErrorHandler.php libs/frontman-wordpress/tests/SentryUnavailableTest.php'

package-wordpress-plugin:
	@VERSION=$(VERSION) bash ./scripts/package-wordpress-plugin.sh

package-openai-wordpress:
	$(MAKE) -C plugins/openai-wordpress release-artifact VERSION="$(VERSION)"

.PHONY: test-wordpress-plugin-export
test-wordpress-plugin-export: package-wordpress-plugin
	@bash ./scripts/test-wordpress-plugin-export.sh

publish-wordpress-plugin-svn: package-wordpress-plugin
	@VERSION=$(VERSION) bash ./scripts/publish-wordpress-plugin-svn.sh

WORDPRESS_TESTS ?= ToolRegistryTest AuthDiagnosticsTest NoFilesystemToolsTest PublicFilesTest ElementorToolsTest MediaToolsTest MutationSnapshotsTest PluginDependenciesTest SeoToolsTest RouterTest SentryTest SentryUnavailableTest

test-wordpress-core-tools: build-wordpress-dependencies
	@set -eu; \
	set -- $(WORDPRESS_TESTS); \
	if [ "$$#" -eq 0 ]; then echo "WORDPRESS_TESTS must select at least one test." >&2; exit 1; fi; \
	for test in "$$@"; do \
		if [ ! -f "libs/frontman-wordpress/tests/$$test.php" ]; then echo "WordPress test file not found: $$test.php" >&2; exit 1; fi; \
	done; \
	for test in "$$@"; do \
		php -d auto_prepend_file=libs/frontman-wordpress/tests/ErrorHandler.php "libs/frontman-wordpress/tests/$$test.php"; \
	done

test-wordpress-runtime:
	@bash scripts/test-wordpress-plugin-runtime.sh




WEBAPI_COMMIT := 5e2d4d5db8257fea0fb3cc2dde5c4699d263a62f

.PHONY: kill-all-processes pull-webapi test-webapi debug-task push

kill-all-processes:
	@ps aux | grep "[m]ake dev" | awk '{print $$2}' | xargs -r kill 2>/dev/null || true

pull-webapi:
	git subtree pull --prefix libs/experimental-rescript-webapi https://github.com/rescript-lang/experimental-rescript-webapi.git $(WEBAPI_COMMIT) --squash

test-webapi:
	yarn workspace @rescript/webapi test

debug-task:
	cd apps/frontman_server && $(MAKE) debug-task ARGS="$(ARGS)"

push:
	@git push