-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathMakefile
More file actions
477 lines (397 loc) · 18.9 KB
/
Makefile
File metadata and controls
477 lines (397 loc) · 18.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
# Frontman Monorepo Makefile
#
# Usage: make [target]
# Run 'make' or 'make help' to see available commands
.DEFAULT_GOAL := help
# Colors for output
CYAN := \033[36m
GREEN := \033[32m
YELLOW := \033[33m
RESET := \033[0m
# Remote development config
# DEVPOD_SERVER is resolved from .env via `op run` (1Password CLI)
# Usage: op run --env-file=.env -- make <target>
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
# Guard: require BRANCH variable
# Usage: $(call require_branch,target-name)
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
# Resolve BRANCH: use provided value or auto-detect from current git branch.
# Sets BRANCH via $(eval) so downstream shell blocks see the correct value.
# Usage: $(call resolve_branch,target-name)
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
# Run an e2e test file (or all tests if no file given)
# Usage: $(call run_e2e,test-file-or-empty)
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 -a && . test/e2e/.env && set +a && cd test/e2e && npx vitest run $(1)
endef
.PHONY: help
# Print help section: $(1)=section label, $(2)=marker prefix (e.g. DEV → DEV_START/DEV_END)
define print_section
@echo ""
@printf "$(CYAN)$(1):$(RESET)\n"
@awk 'BEGIN {FS = ":.*##"} /^## $(2)_START$$/{found=1; next} /^## $(2)_END$$/{found=0} found && /^[a-zA-Z0-9_-]+:.*##/ { printf " $(GREEN)%-25s$(RESET) %s\n", $$1, $$2 }' $(MAKEFILE_LIST)
endef
help: ## Display available commands
@printf "$(CYAN)Frontman Monorepo$(RESET)\n"
$(call print_section,Development,DEV)
$(call print_section,Build & Quality,BUILD)
$(call print_section,SSL & Networking,SSL)
$(call print_section,Worktrees,WT)
$(call print_section,Infrastructure,INFRA)
$(call print_section,Release,REL)
$(call print_section,E2E Tests,E2E)
$(call print_section,Utilities,UTIL)
@echo ""
# ============================================================================
# Development
# ============================================================================
## DEV_START
.PHONY: dev dev-client dev-server dev-nextjs dev-marketing
dev: ## Start all core services (client + server + nextjs)
@printf "$(YELLOW)Starting all services via mprocs...$(RESET)\n"
mprocs --config mprocs.yml
dev-client: ## Start development server for client app
@printf "$(YELLOW)Starting client dev server...$(RESET)\n"
cd libs/client && $(MAKE) dev
dev-server: ## Start development server for server app
@printf "$(YELLOW)Starting server dev server...$(RESET)\n"
cd apps/frontman_server && $(MAKE) dev
dev-nextjs: ## Start development server for Next.js test site
@printf "$(YELLOW)Starting Next.js dev server...$(RESET)\n"
cd test/sites/blog-starter && $(MAKE) dev
dev-marketing: ## Start development server for marketing site
@printf "$(YELLOW)Starting marketing dev server...$(RESET)\n"
cd apps/marketing && $(MAKE) dev
## DEV_END
# ============================================================================
# Build & Quality
# ============================================================================
## BUILD_START
.PHONY: install build rescript-watch rescript-build clean hooks-install
install: ## Install dependencies
@printf "$(YELLOW)Installing dependencies...$(RESET)\n"
yarn install
@$(MAKE) hooks-install
hooks-install: ## Install git pre-commit hooks via Lefthook
@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
build: ## Build ReScript project
@printf "$(YELLOW)Building ReScript project...$(RESET)\n"
yarn rescript
rescript-watch: ## Watch and rebuild ReScript on changes
@printf "$(YELLOW)Starting ReScript watch mode...$(RESET)\n"
yarn rescript watch
rescript-build: ## Build ReScript project (one-shot)
@printf "$(YELLOW)Starting ReScript build...$(RESET)\n"
yarn rescript build
clean: ## Clean ReScript build artifacts
@printf "$(YELLOW)Cleaning build artifacts...$(RESET)\n"
yarn rescript clean
## BUILD_END
# ============================================================================
# E2E Tests
# ============================================================================
## E2E_START
.PHONY: e2e e2e-nextjs e2e-astro e2e-vite e2e-vue-vite
e2e: ## Run all e2e tests (loads secrets from test/e2e/.env)
@printf "$(YELLOW)Running all e2e tests...$(RESET)\n"
$(call run_e2e)
e2e-nextjs: ## Run Next.js e2e test
@printf "$(YELLOW)Running Next.js e2e test...$(RESET)\n"
$(call run_e2e,tests/nextjs.test.ts)
e2e-astro: ## Run Astro e2e test
@printf "$(YELLOW)Running Astro e2e test...$(RESET)\n"
$(call run_e2e,tests/astro.test.ts)
e2e-vite: ## Run Vite e2e test
@printf "$(YELLOW)Running Vite e2e test...$(RESET)\n"
$(call run_e2e,tests/vite.test.ts)
e2e-vue-vite: ## Run Vue + Vite e2e test
@printf "$(YELLOW)Running Vue + Vite e2e test...$(RESET)\n"
$(call run_e2e,tests/vue-vite.test.ts)
## E2E_END
# ============================================================================
# SSL & Networking
# ============================================================================
## SSL_START
.PHONY: ssl-setup tunnel
ssl-setup: ## Setup local SSL certificates using mkcert
@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: ## Start SSH tunnel to DevPod server (fallback if dnsmasq not configured)
$(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
## SSL_END
# ============================================================================
# Worktrees
# ============================================================================
#
# Primary commands (use these):
# make wt Dashboard — status, URLs, actions at a glance
# make wt-new BRANCH=... Create containerized worktree
# make wt-dev BRANCH=... Start dev servers (mprocs TUI)
# make wt-stop BRANCH=... Pause (preserves data)
# make wt-start BRANCH=... Resume paused worktree
# make wt-sh BRANCH=... Shell into container
# make wt-rm BRANCH=... Full cleanup (pod + volumes + worktree)
# make wt-gc Remove worktrees for branches merged into main
# make wt-urls BRANCH=... Show service URLs
# make wt-logs BRANCH=... Tail container logs
#
# All BRANCH= args auto-detect from current git branch when omitted.
#
# Shared variables for containerized worktrees
CADDY_CONTAINER := frontman-caddy
DEV_IMAGE := frontman-dev:latest
# Env vars passed to bin/ scripts so they can compute hashes portably
export MD5CMD := $(shell if command -v md5sum >/dev/null 2>&1; then echo 'md5sum | cut -c1-4'; else echo 'md5 | cut -c1-4'; fi)
## WT_START
.PHONY: wt wt-new wt-dev wt-stop wt-start wt-sh wt-rm wt-gc wt-urls wt-logs work
work: ## Set up worktree from GitHub issue or PR (REF=<number|url>)
@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: ## Dashboard — shows all worktrees, pod status, URLs, and actions
@bash ./bin/wt-dashboard
wt-new: ## Create containerized worktree (BRANCH=...)
$(call resolve_branch,wt-new)
@BRANCH="$(BRANCH)" WORKTREE_BASE_BRANCH="$(WORKTREE_BASE_BRANCH)" DEV_IMAGE=$(DEV_IMAGE) \
bash ./bin/wt-pod-create
wt-dev: ## Start dev servers in container (BRANCH=...)
$(call resolve_branch,wt-dev)
@BRANCH="$(BRANCH)" CADDY_CONTAINER=$(CADDY_CONTAINER) \
bash ./bin/wt-pod-dev
wt-stop: ## Pause worktree pod, preserve volumes (BRANCH=...)
$(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: ## Resume a paused worktree pod (BRANCH=...)
$(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: ## Shell into dev container (BRANCH=...)
$(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: ## Full cleanup: pod + volumes + worktree (BRANCH=...)
$(call resolve_branch,wt-rm)
@BRANCH="$(BRANCH)" bash ./bin/wt-pod-remove
wt-gc: ## Remove worktrees whose branches are merged into main
@bash ./bin/wt-gc
wt-urls: ## Show service URLs for a worktree (BRANCH=...)
$(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)Storybook$(RESET) https://$$HASH.storybook.frontman.local\n"; \
printf " $(GREEN)Marketing$(RESET) https://$$HASH.marketing.frontman.local\n"; \
echo ""
wt-logs: ## Tail dev container logs (BRANCH=...)
$(call resolve_branch,wt-logs)
@CONTAINER=$$(BRANCH="$(BRANCH)" bash ./bin/wt-resolve container) || exit 1; \
podman logs -f "$$CONTAINER"
## WT_END
# ============================================================================
# Infrastructure
# ============================================================================
## INFRA_START
.PHONY: infra-up infra-down infra-build
infra-up: ## One-time setup: dev image, Caddy, dnsmasq
@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: ## Tear down all pods, volumes, and Caddy
@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: ## Rebuild the frontman-dev container image
@podman build -t $(DEV_IMAGE) -f .devcontainer/Dockerfile .devcontainer/
## INFRA_END
# ============================================================================
# Worktree Internals (not shown in help — use wt-* commands above)
# ============================================================================
.PHONY: worktree-create worktree-list worktree-remove worktree-clean \
worktree-register worktree-registry
# Plain worktree management (without containers)
# Auto-detects whether to create a new branch or check out an existing one.
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; \
mkdir -p ".worktrees/$$WORKTREE_NAME/.claude/projects" ".worktrees/$$WORKTREE_NAME/.claude/plans" ".worktrees/$$WORKTREE_NAME/.claude/todos"; \
touch ".worktrees/$$WORKTREE_NAME/.claude/history.jsonl"; \
printf "$(GREEN)Worktree created at: .worktrees/$$WORKTREE_NAME$(RESET)\n"
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'"
# ============================================================================
# Release
# ============================================================================
## REL_START
.PHONY: publish publish-astro publish-vite publish-nextjs publish-react-statestore publish-swarm-ai release package-wordpress-plugin test-wordpress-core-tools
publish: publish-astro publish-vite publish-nextjs publish-react-statestore ## Publish all npm packages (pass OTP=<code> for 2FA)
publish-astro: ## Publish @frontman-ai/astro to npm (pass OTP=<code> for 2FA)
cd libs/frontman-astro && $(MAKE) publish OTP=$(OTP)
publish-vite: ## Publish @frontman-ai/vite to npm (pass OTP=<code> for 2FA)
cd libs/frontman-vite && $(MAKE) publish OTP=$(OTP)
publish-nextjs: ## Publish @frontman-ai/nextjs to npm (pass OTP=<code> for 2FA)
cd libs/frontman-nextjs && $(MAKE) publish OTP=$(OTP)
publish-react-statestore: ## Publish @frontman-ai/react-statestore to npm (pass OTP=<code> for 2FA)
cd libs/react-statestore && $(MAKE) publish OTP=$(OTP)
publish-swarm-ai: ## Publish swarm_ai to Hex (dry run by default, HEX_PUBLISH=1 for real)
cd apps/swarm_ai && $(MAKE) hex-publish HEX_PUBLISH=$(HEX_PUBLISH)
release: ## Create a release PR from pending changesets
@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 "$(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"
package-wordpress-plugin: ## Build WordPress ZIP and WordPress.org bundle
@VERSION=$(VERSION) bash ./scripts/package-wordpress-plugin.sh
test-wordpress-core-tools: ## Run PHP tests for WordPress core tool implementations
@php libs/frontman-wordpress/tests/CoreToolsTest.php
@php libs/frontman-wordpress/tests/ManagedThemeToolsTest.php
@php libs/frontman-wordpress/tests/MutationSnapshotsTest.php
@php libs/frontman-wordpress/tests/RouterTest.php
## REL_END
# ============================================================================
# Utilities
# ============================================================================
## UTIL_START
.PHONY: kill-all-processes pull-webapi debug-task push
kill-all-processes: ## Kill all running make dev processes
@ps aux | grep "[m]ake dev" | awk '{print $$2}' | xargs -r kill 2>/dev/null || true
pull-webapi: ## Pull latest experimental-rescript-webapi subtree
git subtree pull --prefix libs/experimental-rescript-webapi git@github.com:itayadler/experimental-rescript-webapi.git main --squash
debug-task: ## Debug task interactions (ARGS="list" or ARGS="show ...")
cd apps/frontman_server && $(MAKE) debug-task ARGS="$(ARGS)"
push: ## Git push current branch
@git push
## UTIL_END