go-doc/Makefile

288 lines
12 KiB
Makefile
Raw Normal View History

2026-10-07 15:03:50 +02:00
# =============================================================================
# go-doc — documentation & audience feedback platform
#
# make help list every target
# make dev run infrastructure + backend + frontend together
# make test run unit, integration and BDD suites
# make build embed the React bundle into the portable Go binary
# make docker build the all-in-one image
# make docker-push build and push the image to the private registry
# make docker-run run the image on :8080
# make helm-deploy-development deploy to development
# make helm-deploy-production deploy to production
# =============================================================================
SHELL := /bin/bash
.SHELLFLAGS := -eu -o pipefail -c
GO ?= go
NPM ?= npm
COMPOSE ?= docker compose
HELM ?= helm
ROOT := $(shell pwd)
WORKSPACE ?= $(dir $(ROOT))
BACKEND_DIR := $(ROOT)/backend
FRONTEND_DIR := $(ROOT)/frontend
DATA_DIR := $(ROOT)/data
WEBUI_DIST := $(BACKEND_DIR)/webui/dist
BINARY := $(ROOT)/bin/godoc
BACKEND_ADDR ?= 0.0.0.0:8080
HOST_ADDR ?= 127.0.0.1:8080
VITE_PORT ?= 5173
# Docker/Registry
IMAGE ?= go-doc
TAG ?= latest
REGISTRY_IMAGE ?= nexus.urpi.be/docker-hosted/go-doc
# Helm
HELM_CHART := $(ROOT)/helm/go-doc
HELM_RELEASE ?= go-doc
HELM_NAMESPACE ?= go-doc
KUBECONFIG ?= $(HOME)/.kube/config
GO_LOOSE_CA_SECRET ?= local-ca
# Go Loose credentials passed via environment or .env
GO_LOOSE_SET = $(if $(GO_LOOSE_CLIENT_ID),--set-string goLoose.clientID=$(GO_LOOSE_CLIENT_ID)) \
$(if $(GO_LOOSE_CLIENT_SECRET),--set-string goLoose.clientSecret=$(GO_LOOSE_CLIENT_SECRET)) \
$(if $(GO_LOOSE_CA_SECRET),--set-string goLoose.ca.existingSecret=$(GO_LOOSE_CA_SECRET))
# Parent directory that holds the go-* projects to document. Override with
# WORKSPACE=/path/to/projects when the projects live elsewhere.
DOCS_WORKSPACE ?= $(patsubst %/,%,$(WORKSPACE))
DOCGEN_INTERVAL ?= 5s
DOCS_COUNT ?= 120
COMMENTS_PER_DOC ?= 25
SEED_WORKERS ?= 8
GO_SOURCES := $(shell find $(BACKEND_DIR) -name '*.go' -type f 2>/dev/null)
FRONTEND_SOURCES := $(shell find $(FRONTEND_DIR)/src -type f 2>/dev/null)
.DEFAULT_GOAL := help
.PHONY: help infra-up infra-down infra-logs backend-run frontend-run dev test \
test-unit test-integration test-bdd test-race build build-frontend \
build-backend fmt vet tidy seed seed-perf clean distclean run docker-build \
docker docker-push docker-run helm-lint helm-deploy-development \
helm-deploy-production helm-down-development helm-down-production \
docs-sync docs-watch docs-dry-run docs-list
## ---------------------------------------------------------------------------
## Help
## ---------------------------------------------------------------------------
help: ## Show this help
@printf '\n\033[1mgo-doc\033[0m — available targets\n\n'
@grep -hE '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
| sort \
| awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-18s\033[0m %s\n", $$1, $$2}'
@printf '\n'
## ---------------------------------------------------------------------------
## Infrastructure
## ---------------------------------------------------------------------------
infra-up: ## Start local development containers detached
@if $(COMPOSE) version >/dev/null 2>&1; then \
$(COMPOSE) up -d --remove-orphans; \
else \
echo "error: '$(COMPOSE)' is unavailable. Install Docker Compose v2." >&2; \
exit 1; \
fi
infra-down: ## Tear down infrastructure and remove volumes
@if $(COMPOSE) version >/dev/null 2>&1; then \
$(COMPOSE) down --volumes --remove-orphans; \
else \
echo "docker compose unavailable; nothing to tear down"; \
fi
infra-logs: ## Tail infrastructure logs
$(COMPOSE) logs -f
docker-build: ## Build the container images
$(COMPOSE) build
## ---------------------------------------------------------------------------
## Run
## ---------------------------------------------------------------------------
backend-run: ## Run the Go API natively on 0.0.0.0:8080
@mkdir -p $(DATA_DIR)
@echo "→ backend http://$(HOST_ADDR)"
@cd $(BACKEND_DIR) && GO_DOC_DATA_DIR=$(DATA_DIR) $(GO) run main.go -addr $(BACKEND_ADDR)
frontend-run: ## Run the Vite dev server on port 5173
@echo "→ frontend http://$(VITE_PORT)"
@cd $(FRONTEND_DIR) && $(NPM) run dev -- --port $(VITE_PORT)
dev: ## Run backend, frontend and infrastructure concurrently
@$(MAKE) --no-print-directory infra-up
@echo "→ backend http://$(HOST_ADDR)"
@echo "→ frontend http://$(VITE_PORT) (Ctrl-C stops everything)"
@trap 'echo; echo "→ stopping dev stack"; kill 0' EXIT INT TERM; \
( cd $(BACKEND_DIR) && GO_DOC_DATA_DIR=$(DATA_DIR) $(GO) run main.go -addr $(BACKEND_ADDR) ) & \
( cd $(FRONTEND_DIR) && $(NPM) run dev -- --port $(VITE_PORT) ) & \
wait
run: build ## Build the single-file binary and run it
@echo "→ serving SPA + API on http://$(HOST_ADDR)"
@$(BINARY) -addr $(BACKEND_ADDR) -data $(DATA_DIR)
## ---------------------------------------------------------------------------
## Test
## ---------------------------------------------------------------------------
test: fmt vet ## Run every suite: unit, integration, BDD, race
@echo "→ go test ./... -count=1"
@cd $(BACKEND_DIR) && $(GO) test ./... -count=1
@echo "→ race detector on the storage engine and API"
@cd $(BACKEND_DIR) && $(GO) test ./storage/... ./tests/... -race -count=1
@echo "✓ all suites passed"
test-unit: ## Run storage engine unit tests only
@cd $(BACKEND_DIR) && $(GO) test ./storage/... -count=1 -v
test-integration: ## Run end-to-end API tests against the real filesystem
@cd $(BACKEND_DIR) && $(GO) test ./tests/ -run Integration -count=1 -v
test-bdd: ## Run the BDD feature suites
@cd $(BACKEND_DIR) && $(GO) test ./tests/bdd/... -count=1 -v
test-race: ## Run all tests under the race detector
@cd $(BACKEND_DIR) && $(GO) test ./... -race -count=1
## ---------------------------------------------------------------------------
## Build
## ---------------------------------------------------------------------------
build: build-frontend build-backend ## Build the SPA and embed it in the Go binary
@echo "✓ $(BINARY) serves both the SPA and /api"
build-frontend: $(FRONTEND_SOURCES) ## Compile the React bundle into frontend/dist
@cd $(FRONTEND_DIR) && $(NPM) install --no-audit --no-fund
@cd $(FRONTEND_DIR) && $(NPM) run build
build-backend: $(GO_SOURCES) ## Stage assets into webui/dist and compile the binary
@test -f $(FRONTEND_DIR)/dist/index.html || { \
echo "error: frontend/dist/index.html missing — run 'make build-frontend' first" >&2; \
exit 1; \
}
@mkdir -p $(WEBUI_DIST) $(dir $(BINARY))
@rm -rf $(WEBUI_DIST)
@mkdir -p $(WEBUI_DIST)
@cp -r $(FRONTEND_DIR)/dist/. $(WEBUI_DIST)/
@echo "→ embedded assets: $$(find $(WEBUI_DIST) -type f | wc -l) files"
@cd $(BACKEND_DIR) && CGO_ENABLED=0 $(GO) build -trimpath -ldflags '-s -w' -o $(BINARY) .
@echo "✓ built $(BINARY) ($$(du -h $(BINARY) | cut -f1))"
## ---------------------------------------------------------------------------
## Quality
## ---------------------------------------------------------------------------
fmt: ## gofmt the backend
@cd $(BACKEND_DIR) && $(GO) fmt ./... >/dev/null
@echo "✓ gofmt"
vet: ## go vet the backend
@cd $(BACKEND_DIR) && $(GO) vet ./...
@echo "✓ go vet"
tidy: ## Tidy go.mod
@cd $(BACKEND_DIR) && $(GO) mod tidy
## ---------------------------------------------------------------------------
## Documentation generation
## ---------------------------------------------------------------------------
## Every go-* project under WORKSPACE is analysed and imported into data/docs.
## Generated pages are wrapped in <!-- go-doc:generated ---> markers, so a
## re-sync replaces only the machine-written part and keeps anything you wrote
## outside the markers. Pass DOCGEN_ARGS to reach the CLI directly, e.g.
## make docs-sync DOCGEN_ARGS="-apps go-tell -force"
docs-sync: ## Analyse every go-* project under WORKSPACE and import its docs (DOCGEN_ARGS)
@mkdir -p $(DATA_DIR)/docs
@cd $(BACKEND_DIR) && $(GO) run ./scripts/docgen \
-root $(DOCS_WORKSPACE) -data $(DATA_DIR) $(DOCGEN_ARGS)
docs-watch: ## Keep importing documentation as projects change (Ctrl-C to stop)
@mkdir -p $(DATA_DIR)/docs
@cd $(BACKEND_DIR) && $(GO) run ./scripts/docgen \
-root $(DOCS_WORKSPACE) -data $(DATA_DIR) -watch -interval $(DOCGEN_INTERVAL) $(DOCGEN_ARGS)
docs-dry-run: ## Analyse and render without writing anything
@cd $(BACKEND_DIR) && $(GO) run ./scripts/docgen \
-root $(DOCS_WORKSPACE) -data $(DATA_DIR) -dry-run $(DOCGEN_ARGS)
docs-list: ## List the go-* projects that a sync would import
@printf 'workspace: %s\n\n' '$(DOCS_WORKSPACE)'
@ls -1 $(DOCS_WORKSPACE) 2>/dev/null | grep '^go-' | grep -v '^go-doc$$' | sed 's/^/ /'
## ---------------------------------------------------------------------------
## Data
## ---------------------------------------------------------------------------
seed: ## Seed 120 generated documents + thousands of comments (DOCS_COUNT, COMMENTS_PER_DOC)
@cd $(BACKEND_DIR) && $(GO) run ./scripts/seed_perf_data.go \
-data $(DATA_DIR) -docs $(DOCS_COUNT) -comments $(COMMENTS_PER_DOC) -workers $(SEED_WORKERS)
seed-perf: seed ## Alias for seed
## ---------------------------------------------------------------------------
## Clean
## ---------------------------------------------------------------------------
clean: ## Remove build artefacts
@rm -rf $(ROOT)/bin $(FRONTEND_DIR)/dist
@echo "✓ cleaned build artefacts (backend/webui/dist left intact)"
distclean: clean ## Also restore the placeholder frontend bundle
@rm -rf $(WEBUI_DIST)
@mkdir -p $(WEBUI_DIST)
@cp $(BACKEND_DIR)/webui/dist.placeholder/index.html $(WEBUI_DIST)/index.html
@echo "✓ webui/dist reset to the placeholder bundle"
## ---------------------------------------------------------------------------
## Docker
## ---------------------------------------------------------------------------
docker: ## Build the all-in-one image
docker build -t $(IMAGE):$(TAG) -f $(BACKEND_DIR)/Dockerfile .
docker-push: ## Build and push the image to the private registry
docker tag $(IMAGE):$(TAG) $(REGISTRY_IMAGE):$(TAG)
docker push $(REGISTRY_IMAGE):$(TAG)
docker-run: ## Run the image on :8080
docker run --rm -p 8080:8080 --env-file $(ROOT)/.env $(IMAGE):$(TAG)
## ---------------------------------------------------------------------------
## Helm
## ---------------------------------------------------------------------------
helm-lint: ## Lint the helm chart
$(HELM) lint $(HELM_CHART) -f $(HELM_CHART)/values-dev.yaml
$(HELM) lint $(HELM_CHART) -f $(HELM_CHART)/values-prd.yaml
$(HELM) template $(HELM_RELEASE) $(HELM_CHART) \
-f $(HELM_CHART)/values-dev.yaml >/dev/null
$(HELM) template $(HELM_RELEASE) $(HELM_CHART) \
-f $(HELM_CHART)/values-prd.yaml \
--set-string goLoose.clientID=lint --set-string goLoose.clientSecret=lint >/dev/null
# Go Loose credentials are never stored in the values files. Pass them on the
# command line, from a secret store, or with --set-file:
# make helm-deploy-production \
# --set goLoose.clientID=$(GO_LOOSE_CLIENT_ID) \
# --set goLoose.clientSecret=$(GO_LOOSE_CLIENT_SECRET) \
# --set goLoose.ca.existingSecret=local-ca
helm-deploy-development: ## Deploy to development
KUBECONFIG="$(KUBECONFIG)" $(HELM) upgrade --install $(HELM_RELEASE) $(HELM_CHART) \
--namespace $(HELM_NAMESPACE)-development --create-namespace \
-f $(HELM_CHART)/values-dev.yaml \
--rollback-on-failure --wait --timeout 10m
helm-deploy-production: ## Deploy to production
set -a; [ ! -f $(ROOT)/.env ] || . $(ROOT)/.env; set +a; \
KUBECONFIG="$(KUBECONFIG)" $(HELM) upgrade --install $(HELM_RELEASE) $(HELM_CHART) \
--namespace go-insane-prd --create-namespace \
-f $(HELM_CHART)/values-prd.yaml \
$(GO_LOOSE_SET) \
--rollback-on-failure --wait --timeout 10m
helm-down-development: ## Tear down development release
KUBECONFIG="$(KUBECONFIG)" $(HELM) uninstall $(HELM_RELEASE) -n $(HELM_NAMESPACE)-development
helm-down-production: ## Tear down production release
KUBECONFIG="$(KUBECONFIG)" $(HELM) uninstall $(HELM_RELEASE) -n $(HELM_NAMESPACE)-production