Makefile
Rama main · réplica de lectura del repositorio original.
COMPOSE_DEV = podman compose -f compose.dev.yml
COMPOSE_DEMO = podman compose -p jarvis-demo -f compose.demo.yml
COMPOSE_TEST_DEPS = podman compose -f compose.dev-dependencies.yml
COMPOSE_E2E = podman compose -f compose.e2e.yml
GITLEAKS_IMAGE = $(shell cat scripts/gitleaks-image)
GITLEAKS = podman run --rm -v "$(CURDIR):/repo:ro,z" $(GITLEAKS_IMAGE)
FRONTEND_CONTAINER = jarvis_frontend_1
DEMO_AM_URL = http://localhost:$(DEMO_AM_PORT)
.PHONY: help \
setup \
up up-build down logs ps \
up-alertmanager down-alertmanager \
up-alertmanager-ha down-alertmanager-ha \
up-postgres down-postgres \
demo-up demo-seed demo-resolve demo-reset demo-down \
verify test-all test-backend test-frontend test-frontend-unit fuzz-backend \
helm-lint helm-test \
lint gosec govulncheck audit security-all check-agent-context check-image-pins test-scripts \
scan scan-history scan-staged scan-canary scan-all \
build \
e2e-build e2e-down e2e e2e-mode e2e-fast e2e-screenshots e2e-screenshot release-video \
fixtures-create fixtures-remove fixtures-partial-resolve fixtures-refire fixtures-silence fixtures-unsilence \
diagrams \
website website-dev
help: ## Show available targets
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
| awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}'
setup: ## One-time setup: activate pre-commit hooks
git config core.hooksPath .githooks
@echo "Pre-commit hooks activated."
# ── Dev environment ────────────────────────────────────────────────────────────
up: ## Start dev environment (Vite HMR + air hot-reload)
$(COMPOSE_DEV) up
up-build: ## Start dev environment, rebuild images first
$(COMPOSE_DEV) up --build
down: ## Stop dev environment
$(COMPOSE_DEV) down
logs: ## Follow dev logs
$(COMPOSE_DEV) logs -f
ps: ## Show status of all running containers (dev + test deps)
podman compose ps
up-alertmanager: ## Start test Alertmanager (port 9094) — auto-creates network if needed
podman network create jarvis_default 2>/dev/null || true
$(COMPOSE_TEST_DEPS) up -d test-alertmanager
down-alertmanager: ## Stop test Alertmanager
$(COMPOSE_TEST_DEPS) stop test-alertmanager
up-alertmanager-ha: ## Start a 2-member Alertmanager HA gossip pair (ports 9094 + 9095)
podman network create jarvis_default 2>/dev/null || true
$(COMPOSE_TEST_DEPS) --profile ha up -d test-alertmanager test-alertmanager-2
down-alertmanager-ha: ## Stop the HA Alertmanager pair
$(COMPOSE_TEST_DEPS) --profile ha stop test-alertmanager test-alertmanager-2
up-postgres: ## Start test PostgreSQL (port 5432, jarvis/jarvis/jarvis) — auto-creates network if needed
podman network create jarvis_default 2>/dev/null || true
$(COMPOSE_TEST_DEPS) up -d test-postgres
down-postgres: ## Stop test PostgreSQL
$(COMPOSE_TEST_DEPS) stop test-postgres
# ── Demo stack (compose.demo.yml — separate from the dev stack) ─────────────────
# Published image + a throwaway Alertmanager. Own data volume, so demo-reset can
# wipe the history without touching ./data used by the dev stack. The dev stack
# also binds 8080 — run both at once with DEMO_PORT=8081 DEMO_AM_PORT=9096.
DEMO_PORT ?= 8080
DEMO_AM_PORT ?= 9093
DEMO_ENV = JARVIS_DEMO_PORT=$(DEMO_PORT) JARVIS_DEMO_AM_PORT=$(DEMO_AM_PORT)
demo-up: ## Start the demo stack (Jarvis on :8080, Alertmanager on :9093)
$(DEMO_ENV) $(COMPOSE_DEMO) up -d
@echo ""
@echo "Jarvis: http://localhost:$(DEMO_PORT)"
@echo "Alertmanager: http://localhost:$(DEMO_AM_PORT)"
@echo "Next: make demo-seed"
demo-seed: ## Fire the 18 demo alerts into the demo Alertmanager (~2 min)
@ALERTMANAGER_URL=$(DEMO_AM_URL) bash scripts/fire-test-alerts.sh --profile demo
demo-resolve: ## Resolve the demo alerts — they move to Jarvis's Resolved view, history stays
@ALERTMANAGER_URL=$(DEMO_AM_URL) bash scripts/resolve-test-alerts.sh --profile demo
demo-reset: ## Wipe the demo stack completely (alerts + Jarvis history) and start it fresh
$(DEMO_ENV) $(COMPOSE_DEMO) down -v
$(DEMO_ENV) $(COMPOSE_DEMO) up -d
@echo "Demo reset. Next: make demo-seed"
demo-down: ## Remove the demo stack completely — containers and its data volume
$(DEMO_ENV) $(COMPOSE_DEMO) down -v
# ── Tests ──────────────────────────────────────────────────────────────────────
verify: ## Verify the working tree end-to-end: every CI gate + PostgreSQL tests + prod image smoke test (FAST=1 skips the image)
@FAST=$(FAST) scripts/verify.sh
test-all: test-backend test-frontend-unit test-frontend helm-lint helm-test ## Run all tests (backend + frontend + helm)
test-backend: ## Backend: go test -race ./...
cd backend && go test -v -race ./...
# Execution counts (Nx) per target, not a duration: a time budget can fail a clean
# run with "context deadline exceeded" (golang/go#75804). Same counts as CI.
# FUZZTIME=<count|duration> overrides all targets (e.g. FUZZTIME=2000000x or 5m).
fuzz-backend: ## Backend: run all Go native fuzz targets (per-target execution counts; FUZZTIME= overrides)
cd backend && go test ./internal/db -run '^$$' -fuzz '^FuzzRedactDSN$$' -fuzztime $(or $(FUZZTIME),150000x)
cd backend && go test ./internal/history -run '^$$' -fuzz '^FuzzParseNullableTimeString$$' -fuzztime $(or $(FUZZTIME),100000x)
cd backend && go test ./internal/config -run '^$$' -fuzz '^FuzzParseSecretKey$$' -fuzztime $(or $(FUZZTIME),1000000x)
cd backend && go test ./internal/api -run '^$$' -fuzz '^FuzzValidateSilenceMatchers$$' -fuzztime $(or $(FUZZTIME),300000x)
cd backend && go test ./internal/api -run '^$$' -fuzz '^FuzzSanitizeAMMessage$$' -fuzztime $(or $(FUZZTIME),300000x)
test-frontend: ## Frontend: functional E2E tests across all auth modes
$(E2E_RUN) test none
$(E2E_RUN) test internal
$(E2E_RUN) test oidc
test-frontend-unit: ## Frontend: Vitest unit tests + 100% coverage gate (lib/alertUtils.ts only)
podman exec $(FRONTEND_CONTAINER) sh -c "cd /app && pnpm test:unit:coverage"
helm-lint: ## Helm: lint chart
helm lint charts/jarvis/
helm-test: ## Helm: run unit tests (requires helm-unittest plugin)
helm unittest charts/jarvis/
# ── Lint & static analysis ─────────────────────────────────────────────────────
lint: ## golangci-lint
cd backend && golangci-lint run ./...
gosec: ## gosec — hardcoded secrets + SQL injection in Go
cd backend && gosec ./...
govulncheck: ## govulncheck — CVEs in Go dependencies
cd backend && govulncheck ./...
audit: ## pnpm audit — CVEs in frontend dependencies (requires dev container running)
podman exec $(FRONTEND_CONTAINER) sh -c "cd /app && pnpm audit --audit-level=high"
security-all: gosec govulncheck audit ## Run all security tools (gosec + govulncheck + audit)
check-agent-context: ## Validate agent context: adapters, skills, AGENTS.md, paths, fixtures, invariants
scripts/check-agent-context.sh
test-scripts: ## Test the release body, SBOM and image-pin scripts; no network needed
scripts/test-release-body.sh
scripts/test-sbom.sh
scripts/test-check-image-pins.sh
scripts/test-check-go-coverage.sh
scripts/test-check-dependabot-cooldown.sh
scripts/test-third-party-licenses.sh
check-image-pins: ## Every Containerfile FROM has a @sha256 digest, pnpm installs are pinned
scripts/check-image-pins.sh
# ── Secret scanning ────────────────────────────────────────────────────────────
scan: ## gitleaks: scan all source files (respects .gitleaks.toml)
$(GITLEAKS) detect --source=/repo --no-git --verbose
scan-history: ## gitleaks: scan full git history
podman run --rm -v "$(CURDIR):/repo:ro,z" -w /repo $(GITLEAKS_IMAGE) detect --verbose
scan-staged: ## gitleaks: scan staged changes only (mirrors pre-commit behavior)
git diff --cached | $(GITLEAKS) detect --pipe --redact
scan-canary: ## gitleaks: assert .gitleaks.toml still detects synthetic secrets
scripts/check-gitleaks-canary.sh
scan-all: scan scan-history scan-staged scan-canary ## gitleaks: run all scans (files + history + staged + canary)
# ── Build ──────────────────────────────────────────────────────────────────────
build: ## Build production container image locally
podman build -f Containerfile -t jarvis:local .
# ── Diagrams (Mermaid sources in docs/diagrams/, rendered to docs/assets/) ─────
# --user 0: rootless podman maps container root to the host user, so the
# rendered files land with the correct ownership (the image's default node
# user cannot write into the mounted repo).
MERMAID = podman run --rm --user 0 -v "$(CURDIR):/data:z" docker.io/minlag/mermaid-cli:latest
diagrams: ## Render all Mermaid sources as light/dark SVG pairs in docs/assets/
@for f in docs/diagrams/*.mmd; do \
base="docs/assets/$$(basename $$f .mmd)"; \
$(MERMAID) -i "/data/$$f" -o "/data/$${base}-light.svg" -t neutral -b transparent && \
$(MERMAID) -i "/data/$$f" -o "/data/$${base}-dark.svg" -t dark -b transparent && \
echo "rendered $${base}-{light,dark}.svg"; \
done
# ── Docs website (VitePress in website/, content synced from the repo docs) ────
# git is required inside the container: the edit links and "last updated" read
# the real source files via `git log` (.agents/skills/website/SKILL.md).
# --user 0: rootless podman maps container root to the host user, so
# node_modules/ and .vitepress/dist/ land with the correct ownership.
WEBSITE_OPTS = --rm --user 0 -v "$(CURDIR):/repo:z" -v jarvis_website_pnpmstore:/pnpm-store -w /repo/website
WEBSITE_IMAGE = node:26-alpine
WEBSITE_SETUP = apk add --no-cache git >/dev/null && git config --global --add safe.directory /repo && npm install -g pnpm@11.9.0 --prefix /usr/local >/dev/null && pnpm config set store-dir /pnpm-store && pnpm install
website: ## Build the docs website (output: website/.vitepress/dist)
podman run $(WEBSITE_OPTS) $(WEBSITE_IMAGE) sh -c "$(WEBSITE_SETUP) && pnpm run build"
website-dev: ## Serve the docs website with hot reload on http://localhost:5174
podman run $(WEBSITE_OPTS) -it -p 5174:5174 $(WEBSITE_IMAGE) sh -c "$(WEBSITE_SETUP) && pnpm run dev"
# ── E2E + Screenshots (isolated Playwright stack: compose.e2e.yml) ───────────────
# All targets bring the stack up fresh, run, then tear it down. The auth mode
# (none / internal / oidc) is handled by scripts/e2e-run.sh, which boots the
# stack once per mode. See docs/testing-e2e.md for the full guide.
E2E_RUN = bash scripts/e2e-run.sh
MODE ?= none
e2e-build: ## Build the isolated e2e Jarvis image (prod frontend + e2e seed endpoints)
$(COMPOSE_E2E) build e2e-jarvis
e2e-down: ## Force-stop and remove the e2e stack (ephemeral — all data lost)
$(COMPOSE_E2E) down -v
e2e: ## Run the functional suite across ALL auth modes (none + internal + oidc)
$(E2E_RUN) test none
$(E2E_RUN) test internal
$(E2E_RUN) test oidc
e2e-mode: ## Run the functional suite for ONE mode: make e2e-mode MODE=oidc [E2E_SHARD=1/3]
$(E2E_RUN) test $(MODE)
e2e-fast: ## Local-only faster run of ONE mode (image build skipped if unchanged, pnpm store cached): make e2e-fast [MODE=none]
E2E_FAST=1 $(E2E_RUN) test $(MODE)
e2e-screenshots: ## Regenerate ALL screenshots across all modes into docs/assets/
$(E2E_RUN) screenshots none
$(E2E_RUN) screenshots internal
$(E2E_RUN) screenshots oidc
@echo "Screenshots written to docs/assets/"
e2e-screenshot: ## Regenerate ONE screenshot: make e2e-screenshot NAME=card-view [MODE=none]
@test -n "$(NAME)" || { echo "usage: make e2e-screenshot NAME=<test-name> [MODE=none]"; exit 1; }
$(E2E_RUN) screenshot $(MODE) "$(NAME)"
@echo "Screenshot '$(NAME)' written to docs/assets/"
release-video: ## Record + render a demo video: make release-video VERSION=1.13.0 [STEP=all|tts|record|render] [PROJECT=release|intro]
@test -n "$(VERSION)" || { echo "usage: make release-video VERSION=<x.y.z|name> [STEP=all|tts|record|render] [PROJECT=release]"; exit 1; }
VIDEO_PROJECT=$(or $(PROJECT),release) bash scripts/release-video.sh "$(VERSION)" $(or $(STEP),all)
# ── Fixtures ───────────────────────────────────────────────────────────────────
fixtures-create: ## Fire 27 Kubernetes-themed test alerts (test_suite=jarvis) to Alertmanager, all at once
@bash scripts/fire-test-alerts.sh --no-pause
fixtures-remove: ## Resolve all test alerts fired by fixtures-create
@bash scripts/resolve-test-alerts.sh
fixtures-partial-resolve: ## Resolve only three test cases: one single alert, one alert in a group, one whole group (CASES="single member group")
@bash scripts/resolve-test-alerts-partial.sh $(CASES)
fixtures-refire: ## Resolve, wait out the 60s grace period, re-fire — guarantees a new occurrence (~3-4 min)
@bash scripts/refire-test-alerts.sh
fixtures-silence: ## Create an escaped regex silence in Alertmanager (recreate-bug repro)
@bash scripts/create-test-silence.sh
fixtures-unsilence: ## Expire test silences created by fixtures-silence
@bash scripts/resolve-test-silence.sh