Makefile
Rama main · réplica de lectura del repositorio original.
.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