# ============================================================================= # 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 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