go-bananas/Makefile

496 lines
16 KiB
Makefile
Raw Permalink Normal View History

2026-10-07 15:11:35 +02:00
# Go Bananas!!
#
# The Makefile is the primary interface to this repository. Every task a developer
# or a CI job needs is a target here, and each target is safe to run repeatedly.
#
# Conventions:
# * `make help` lists everything.
# * Targets are grouped by phase and the phases run in order for `make all`.
# * Anything that changes the machine (installing a tool, pushing an image) is
# marked ##@destructive so it is obvious in `make help`.
# * Variables at the top are overridable: `make IMAGE_TAG=v2 build`.
#
# The `##@` markers are read by the self-documenting help target, which is why
# they are inline comments rather than a separate block.
SHELL := /usr/bin/env bash
.SHELLFLAGS := -eu -o pipefail -c
.DEFAULT_GOAL := help
.DELETE_ON_ERROR:
.SUFFIXES:
# --------------------------------------------------------------- variables ----
BINARY := go-bananas
CMD := ./cmd/go-bananas
BIN_DIR := bin
DIST_DIR := dist
WEB_DIR := web
SDK_DIR := sdk
VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev)
COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
BUILD_DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
2026-10-10 02:30:01 +02:00
REGISTRY ?= nexus.urpi.be/docker-hosted
2026-10-07 15:11:35 +02:00
IMAGE_NAME ?= $(REGISTRY)/$(BINARY)
IMAGE_TAG ?= $(VERSION)
PLATFORM ?= linux/amd64,linux/arm64
GO ?= go
NPM ?= npm
# Development tool versions, pinned so that every contributor and every CI run use
# the same linter. They are pinned here rather than in a separate tools module
# because `go install pkg@version` resolves the version itself and refuses a
# -modfile, which would make a tools.mod decorative rather than binding. `make
# tools-upgrade` bumps all three in one place.
AIR_VERSION ?= v1.67.4
GOLANGCI_VERSION ?= v2.14.0
SQLC_VERSION ?= v1.31.1
HELM ?= helm
DOCKER ?= docker
# Module cache and build cache live outside the tree so that a bind-mounted
# working directory does not slowly fill the developer's disk.
GO_CACHE ?= $(CURDIR)/.cache/go-build
GOMODCACHE ?= $(CURDIR)/.cache/go-mod
# The Go packages belonging to this module, excluding the JavaScript trees.
#
# Go does not ignore node_modules: any directory under the module root with Go
# files in it is part of `./...`, and a dependency that happens to ship a stray
# package would then be compiled and linted as if it were ours. `./internal/...`
# and `./cmd/...` are listed instead of filtering, so the omission is visible here
# rather than hidden in a pipe.
GO_PACKAGES = ./cmd/... ./internal/...
# The full set of ldflags. Kept in one variable so that `go build`, `go install`
# and goreleaser cannot drift apart and produce differently-identified binaries.
LDFLAGS := -s -w \
-X github.com/urpi/go-bananas/internal/app.Version=$(VERSION) \
-X github.com/urpi/go-bananas/internal/app.Commit=$(COMMIT) \
-X github.com/urpi/go-bananas/internal/app.BuildDate=$(BUILD_DATE)
GOFLAGS_BUILD := -trimpath -ldflags "$(LDFLAGS)"
.DEFAULT_COLOR := \033[0m
.BOLD := \033[1m
.DIM := \033[2m
.GREEN := \033[32m
.YELLOW := \033[33m
.RED := \033[31m
##@ General
.PHONY: help
help: ## Show this help
@printf "$(BOLD)go-bananas$(DEFAULT_COLOR) — runtime design-token service\n\n"
@printf "$(DIM)Version$(DEFAULT_COLOR) $(VERSION) ($(COMMIT))\n\n"
@# The awk pass reads both the "##@" group markers and the "##" target help in
@# one scan so that groups appear in file order rather than in two separate
@# alphabetical lists. A trailing "##@destructive" is stripped from the
@# description and rendered as a marker instead.
@awk 'BEGIN {FS = ":.*##"} \
/^##@/ { printf "\n$(BOLD)%s$(DEFAULT_COLOR)\n", substr($$0, 5); next } \
/^[a-zA-Z0-9_-]+:.*?##/ { \
help = $$0; sub(/^[^:]*:.*## /, "", help); \
mark = ""; \
if (help ~ /##@destructive/) { mark = "$(YELLOW) [modifies this machine]$(DEFAULT_COLOR)"; \
sub(/ *##@destructive/, "", help) } \
printf " $(BOLD)%-20s$(DEFAULT_COLOR) %s%s\n", $$1, help, mark \
}' $(MAKEFILE_LIST)
@printf "\n$(DIM)Override any variable, e.g. make IMAGE_TAG=v2.1.0 image-push$(DEFAULT_COLOR)\n"
.PHONY: version
version: ## Print the version that would be built
@echo "$(VERSION) ($(COMMIT)) built $(BUILD_DATE)"
##@ Development
.PHONY: install
install: ## Install Go and Node dependencies
$(GO) mod download
cd $(WEB_DIR) && $(NPM) install
cd $(SDK_DIR) && $(NPM) install
.PHONY: dev
dev: ## Run the API with hot reload (Ctrl-C to stop)
@command -v air >/dev/null 2>&1 || { \
echo "air is not installed; run 'make tools' or see docs/development.md"; exit 1; }
@BANANAS_ENVIRONMENT=development \
BANANAS_AUTH_MODE=dev \
BANANAS_HTTP_ADDR=:8080 \
BANANAS_DATABASE_PATH=./data/go-bananas.db \
BANANAS_STATIC_DIR=$(WEB_DIR)/dist \
BANANAS_SDK_DIR=$(SDK_DIR)/dist \
air -c .air.toml
.PHONY: dev-api
2026-10-10 02:30:01 +02:00
dev-api: ## Run the API (no SPA); Vite serves the UI, this serves the runtime SDK
2026-10-07 15:11:35 +02:00
@BANANAS_ENVIRONMENT=development \
BANANAS_AUTH_MODE=dev \
BANANAS_HTTP_ADDR=:8080 \
BANANAS_DATABASE_PATH=./data/go-bananas.db \
2026-10-10 02:30:01 +02:00
BANANAS_STATIC_DIR= \
BANANAS_SDK_DIR=$(SDK_DIR)/dist \
2026-10-07 15:11:35 +02:00
$(GO) run -ldflags "$(LDFLAGS)" $(CMD)
.PHONY: dev-web
dev-web: ## Run the Vite dev server for the admin UI, proxying the API
cd $(WEB_DIR) && $(NPM) run dev
.PHONY: dev-all
2026-10-10 02:30:01 +02:00
dev-all: build-sdk ## Run the API and the frontend dev server together
2026-10-07 15:11:35 +02:00
@echo "API on http://localhost:8080, admin UI on http://localhost:5173"
@trap 'kill 0' EXIT; \
$(MAKE) dev-api & \
$(MAKE) dev-web & \
wait
.PHONY: tools
tools: ## Install the pinned development tools (air, golangci-lint, sqlc) ##@destructive
$(GO) install github.com/air-verse/air@$(AIR_VERSION)
$(GO) install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCI_VERSION)
$(GO) install github.com/sqlc-dev/sqlc/cmd/sqlc@$(SQLC_VERSION)
@echo "Tools installed into $$( $(GO) env GOBIN )"
@echo "golangci-lint $(GOLANGCI_VERSION), air $(AIR_VERSION), sqlc $(SQLC_VERSION)"
.PHONY: tools-upgrade
tools-upgrade: ## Bump the pinned development tools to their latest releases ##@destructive
@for tool in \
github.com/air-verse/air:AIR_VERSION \
github.com/golangci/golangci-lint/v2:GOLANGCI_VERSION \
github.com/sqlc-dev/sqlc:SQLC_VERSION; do \
pkg=$${tool%%:*}; var=$${tool##*:}; \
latest=$$($(GO) list -m -versions $$pkg | tr ' ' '\n' | tail -1); \
[ -n "$$latest" ] || { echo "could not resolve $$pkg"; exit 1; }; \
sed -i.bak "s|^$$var .*|$$var ?= $$latest|" Makefile && rm -f Makefile.bak; \
echo "$$var -> $$latest"; \
done
@echo "Run 'make tools' to install them and commit the Makefile change."
.PHONY: clean
clean: ## Remove build output and local data
rm -rf $(BIN_DIR) $(DIST_DIR) data .cache
cd $(WEB_DIR) && rm -rf dist
cd $(SDK_DIR) && rm -rf dist
@echo "Cleaned."
.PHONY: clean-cache
clean-cache: ## Remove every build cache, including the module cache ##@destructive
$(GO) clean -cache -modcache
rm -rf .cache
cd $(WEB_DIR) && rm -rf node_modules/.vite
cd $(SDK_DIR) && rm -rf node_modules/.vite
##@ Build
.PHONY: build
build: build-go build-web build-sdk ## Build every artefact
.PHONY: build-go
build-go: ## Build the server binary into bin/
@mkdir -p $(BIN_DIR)
CGO_ENABLED=0 $(GO) build -trimpath -ldflags "$(LDFLAGS)" -o $(BIN_DIR)/$(BINARY) $(CMD)
@echo "Built $(BIN_DIR)/$(BINARY)"
.PHONY: build-web
build-web: ## Build the admin UI into web/dist
cd $(WEB_DIR) && $(NPM) run build
.PHONY: build-sdk
build-sdk: ## Build the runtime SDK bundles into sdk/dist
cd $(SDK_DIR) && $(NPM) run build
.PHONY: run
run: build-go ## Build and run the server with the current assets
@mkdir -p data
BANANAS_ENVIRONMENT=development \
BANANAS_AUTH_MODE=dev \
BANANAS_STATIC_DIR=$(WEB_DIR)/dist \
BANANAS_SDK_DIR=$(SDK_DIR)/dist \
./$(BIN_DIR)/$(BINARY)
.PHONY: release
release: ## Build release binaries for every supported platform ##@destructive
@command -v goreleaser >/dev/null 2>&1 || { \
echo "goreleaser is not installed; see docs/development.md"; exit 1; }
goreleaser release --clean
.PHONY: release-snapshot
release-snapshot: ## Build release binaries without publishing ##@destructive
goreleaser release --snapshot --clean --skip=publish
.PHONY: sbom
sbom: ## Generate a CycloneDX SBOM for the current version ##@destructive
@command -v syft >/dev/null 2>&1 || { \
echo "syft is not installed"; exit 1; }
syft dir:. -o cyclonedx-json=sbom-$(VERSION).json
##@ Quality
.PHONY: check
check: fmt-check lint test ## Run every check
.PHONY: fmt
fmt: ## Format the Go code
$(GO) fmt ./...
.PHONY: fmt-check
fmt-check: ## Fail if the Go code is not formatted
@unformatted="$$(gofmt -l . | grep -v '^vendor/' || true)"; \
if [ -n "$$unformatted" ]; then \
echo "These files are not gofmt'd:"; echo "$$unformatted"; exit 1; \
fi
@echo "Go formatting is clean."
.PHONY: lint
lint: lint-go lint-web lint-sdk ## Run every linter
.PHONY: lint-go
lint-go: ## Run golangci-lint
@command -v golangci-lint >/dev/null 2>&1 || { \
echo "golangci-lint is not installed; run 'make tools'"; exit 1; }
golangci-lint run $(GO_PACKAGES)
.PHONY: lint-web
lint-web: ## Lint and typecheck the admin UI
cd $(WEB_DIR) && $(NPM) run lint
cd $(WEB_DIR) && $(NPM) run typecheck
.PHONY: lint-sdk
lint-sdk: ## Lint and typecheck the runtime SDK
cd $(SDK_DIR) && $(NPM) run typecheck
.PHONY: fmt-web
fmt-web: ## Format the frontend and SDK sources
cd $(WEB_DIR) && $(NPM) run format
cd $(SDK_DIR) && $(NPM) exec prettier --write .
.PHONY: tidy
tidy: ## Tidy the Go module
$(GO) mod tidy
##@ Test
.PHONY: test
test: test-go test-web test-sdk ## Run every test suite
.PHONY: test-go
test-go: ## Run the Go tests
$(GO) test -race -count=1 $(GO_PACKAGES)
.PHONY: test-cover
test-cover: ## Run the Go tests and write a coverage profile
$(GO) test -race -count=1 -coverprofile=coverage.out -covermode=atomic $(GO_PACKAGES)
$(GO) tool cover -func=coverage.out | tail -1
@echo "Profile written to coverage.out"
.PHONY: test-web
test-web: ## Run the admin UI tests
cd $(WEB_DIR) && $(NPM) test
.PHONY: test-sdk
test-sdk: ## Run the runtime SDK tests
cd $(SDK_DIR) && $(NPM) test
.PHONY: bench
bench: ## Run the token engine benchmarks
$(GO) test -bench=. -benchmem ./internal/tokenengine/
##@ Docker
.PHONY: image
image: ## Build the container image for the host architecture
$(DOCKER) buildx build \
--build-arg VERSION=$(VERSION) \
--build-arg COMMIT=$(COMMIT) \
--build-arg BUILD_DATE=$(BUILD_DATE) \
--tag $(IMAGE_NAME):$(IMAGE_TAG) \
--tag $(IMAGE_NAME):latest \
--load \
.
.PHONY: image-multi
image-multi: ## Build the multi-architecture image without pushing ##@destructive
$(DOCKER) buildx build \
--platform $(PLATFORM) \
--build-arg VERSION=$(VERSION) \
--build-arg COMMIT=$(COMMIT) \
--build-arg BUILD_DATE=$(BUILD_DATE) \
--tag $(IMAGE_NAME):$(IMAGE_TAG) \
.
.PHONY: image-push
image-push: ## Build and push the multi-architecture image ##@destructive
$(DOCKER) buildx build \
--platform $(PLATFORM) \
--build-arg VERSION=$(VERSION) \
--build-arg COMMIT=$(COMMIT) \
--build-arg BUILD_DATE=$(BUILD_DATE) \
--push \
--tag $(IMAGE_NAME):$(IMAGE_TAG) \
.
.PHONY: image-run
image-run: ## Run the image locally on port 8080
$(DOCKER) run --rm -it \
-p 8080:8080 \
-e BANANAS_ENVIRONMENT=development \
-e BANANAS_AUTH_MODE=dev \
-v go-bananas-data:/srv/data \
$(IMAGE_NAME):$(IMAGE_TAG)
.PHONY: compose-up
compose-up: ## Start the local stack with docker compose
$(DOCKER) compose up --build
.PHONY: compose-down
compose-down: ## Stop the local stack ##@destructive
$(DOCKER) compose down
.PHONY: compose-logs
compose-logs: ## Follow the local stack logs
$(DOCKER) compose logs -f
.PHONY: compose-shell
compose-shell: ## Open a shell in the running service ##@destructive
$(DOCKER) compose exec go-bananas /bin/sh
##@ Helm
.PHONY: helm-lint
helm-lint: ## Lint the chart
$(HELM) lint deploy/helm/go-bananas --strict
.PHONY: helm-matrix
helm-matrix: ## Render the chart across a matrix of valid and invalid configurations
./hack/helm-matrix.sh
.PHONY: helm-template
helm-template: ## Render the chart with the default values
$(HELM) template go-bananas deploy/helm/go-bananas
.PHONY: helm-template-auth
helm-template-auth: ## Render the chart configured for OIDC
$(HELM) template go-bananas deploy/helm/go-bananas \
--set identity.mode=oidc \
2026-10-10 02:30:01 +02:00
--set identity.oidc.issuer=https://auth.lcl-urpi.be/realms/urpi \
2026-10-07 15:11:35 +02:00
--set identity.oidc.clientId=go-bananas \
--set identity.oidc.generateSecret=false \
--set identity.oidc.existingSecret=go-bananas-oidc \
--set config.publicBaseUrl=https://themes-dev.example.com
.PHONY: helm-install
helm-install: ## Install the chart into the current kube context ##@destructive
$(HELM) upgrade --install go-bananas deploy/helm/go-bananas \
--namespace go-bananas --create-namespace \
--values deploy/helm/go-bananas/values.yaml \
--wait --timeout 5m
.PHONY: helm-uninstall
helm-uninstall: ## Remove the release ##@destructive
$(HELM) uninstall go-bananas --namespace go-bananas
2026-10-10 02:30:01 +02:00
##@ Local (lcl)
# The lcl environment is the developer's local k3s cluster with a registry at
# localhost:5000 and a TLS edge serving *.intern-urpi.be. go-* apps deploy into the
# shared `go-insane-lcl` namespace. These targets use values-lcl.yaml.
#
# KUBECONFIG — the lcl kubeconfig. On this box the `local` kubecontext goes through a
# Rancher proxy that can 502; the direct k3s config is /etc/rancher/k3s/k3s.yaml.
# Set it to deploy without depending on the proxy: KUBECONFIG=/etc/rancher/k3s/k3s.yaml make deploy-lcl
# LCL_TAG — image tag. Defaults to a timestamp so a rebuild is always re-pulled;
# `IfNotPresent` would otherwise serve a stale `0.0.1` from the node cache.
LCL_REGISTRY ?= localhost:5000
LCL_REPO ?= go-bananas
LCL_TAG ?= $(shell date -u +%Y%m%d%H%M%S)
LCL_NAMESPACE ?= go-insane-lcl
LCL_VALUES = deploy/helm/go-bananas/values-lcl.yaml
.PHONY: image-lcl
image-lcl: ## Build and push the image to the local registry ##@destructive
$(DOCKER) buildx build \
--build-arg VERSION=$(VERSION) \
--build-arg COMMIT=$(COMMIT) \
--build-arg BUILD_DATE=$(BUILD_DATE) \
--tag $(LCL_REGISTRY)/$(LCL_REPO):$(LCL_TAG) \
--push \
.
.PHONY: helm-template-lcl
helm-template-lcl: ## Render the lcl Helm manifest
$(HELM) template go-bananas deploy/helm/go-bananas \
--namespace $(LCL_NAMESPACE) \
--values $(LCL_VALUES) \
--set-string image.tag=$(LCL_TAG)
.PHONY: helm-install-lcl
helm-install-lcl: ## Deploy go-bananas to the local (lcl) environment ##@destructive
$(HELM) upgrade --install go-bananas deploy/helm/go-bananas \
--namespace $(LCL_NAMESPACE) --create-namespace \
--values $(LCL_VALUES) \
--set-string image.tag=$(LCL_TAG) \
--wait --timeout 5m
.PHONY: helm-uninstall-lcl
helm-uninstall-lcl: ## Remove the lcl release ##@destructive
$(HELM) uninstall go-bananas --namespace $(LCL_NAMESPACE)
.PHONY: deploy-lcl
deploy-lcl: image-lcl helm-install-lcl ## Build, push, deploy to lcl, then verify ##@destructive
@echo "waiting for rollout ($(LCL_NAMESPACE)/go-bananas)..."
@kubectl -n $(LCL_NAMESPACE) rollout status deploy/go-bananas --timeout=120s
@echo "=== runtime manifest through the TLS edge ==="
@curl -sk https://bananas-lcl.intern-urpi.be/rt/v1/manifest.json -o /dev/null -w 'manifest.json -> %{http_code}\n' || true
@echo "deployed image: $(LCL_REGISTRY)/$(LCL_REPO):$(LCL_TAG)"
2026-10-07 15:11:35 +02:00
##@ Docs
.PHONY: docs-serve
docs-serve: ## Serve the documentation locally
@command -v mkdocs >/dev/null 2>&1 || { echo "mkdocs is not installed"; exit 1; }
mkdocs serve
.PHONY: docs-build
docs-build: ## Build the documentation ##@destructive
mkdocs build --strict
.PHONY: api-spec
api-spec: ## Print the OpenAPI document summary
@grep -E '^\s{2}/' openapi.yaml | sed 's/:$//' | tr -d ' ' | sed 's|^| |'
##@ Contract
.PHONY: check-contract
check-contract: ## Drive the shared styling contract and the shipped controls in a real browser ##@destructive
./hack/check-contract.sh
.PHONY: check-pilot
check-pilot: ## Prove both adopting applications follow the contract without editing their CSS ##@destructive
./hack/check-pilot-go-loose.sh
./hack/check-pilot-go-tell.sh
##@ CI helpers
.PHONY: ci
ci: check ## Everything CI runs, in one target
.PHONY: verify
verify: ci helm-lint helm-matrix check-contract check-pilot ## The full pre-merge gate
.PHONY: deps-outdated
deps-outdated: ## Report outdated Go and Node dependencies
$(GO) list -u -m all
@echo
cd $(WEB_DIR) && $(NPM) outdated || true
.PHONY: deps-update
deps-update: ## Update Go and Node dependencies ##@destructive
$(GO) get -u ./... && $(GO) mod tidy
cd $(WEB_DIR) && $(NPM) update
cd $(SDK_DIR) && $(NPM) update