.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=) 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= for 2FA) HELP_publish-astro := Publish @frontman-ai/astro to npm (pass OTP= for 2FA) HELP_publish-vite := Publish @frontman-ai/vite to npm (pass OTP= for 2FA) HELP_publish-nextjs := Publish @frontman-ai/nextjs to npm (pass OTP= for 2FA) HELP_publish-react-statestore := Publish @frontman-ai/react-statestore to npm (pass OTP= 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 &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 &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=$(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