BINARY_NAME=leafwiki CMD_DIR=./cmd/leafwiki VERSION ?= $(shell ./scripts/resolve-version.sh) RELEASE_DIR := releases DOCKER_BUILDER := Dockerfile.builder UI_DIR := ui/leafwiki-ui HTTP_DIST := internal/http/dist # Single source for the golangci-lint version; the lint-go job in # .github/workflows/backend.yml reads the same file. GOLANGCI_LINT_VERSION ?= $(shell cat .golangci-lint-version) # golang.org/x/tools release for `make deadcode` (not a go.mod dependency). DEADCODE_VERSION ?= v0.52.0 EMBED_LDFLAGS := -X github.com/perber/wiki/internal/http.EmbedFrontend=true -X github.com/perber/wiki/internal/http.Environment=production LDFLAGS := -X main.Version=$(VERSION) $(EMBED_LDFLAGS) PLATFORMS := \ linux/amd64 \ linux/386 \ linux/arm64 \ windows/amd64 \ darwin/amd64 \ darwin/arm64 # Release targets other than linux, checked by `make cross` (and in CI) so a # platform-specific compile error doesn't first show up at the release tag. CROSS_PLATFORMS ?= windows/amd64 darwin/arm64 all: build # Build the Vite SPA into internal/http/dist for go:embed. ui: @echo "Building frontend..." cd $(UI_DIR) && npm ci --ignore-scripts && VITE_API_URL=/ APP_VERSION=$(VERSION) npm run build rm -rf $(HTTP_DIST) mkdir -p $(HTTP_DIST) cp -R $(UI_DIR)/dist/. $(HTTP_DIST)/ touch $(HTTP_DIST)/.gitkeep @test -f $(HTTP_DIST)/index.html || (echo "Frontend build missing $(HTTP_DIST)/index.html" && exit 1) # Self-contained binary with embedded UI (matches release/Docker builds). build: ui go build -trimpath -tags nomsgpack -ldflags "$(LDFLAGS)" -o $(BINARY_NAME) $(CMD_DIR) # API-only binary for local Vite-proxied development (no embedded SPA). build-api: go build -ldflags "-X main.Version=$(VERSION)" -o $(BINARY_NAME) $(CMD_DIR) # npm dependencies for the frontend (ui-deps) and the e2e tests. Install # scripts stay off, like in CI and `make ui`. setup: ui-deps cd e2e && npm ci --ignore-scripts # Only for running the E2E suites locally (make run-e2e-local); Chromium is # the browser the suites use. On Linux, `npx playwright install-deps chromium` # in e2e/ adds missing system libraries (needs sudo). setup-e2e: setup cd e2e && npx playwright install chromium # Toolchain versions against .nvmrc and go.mod; exits 1 on a missing tool or # the wrong Node major. doctor: @./scripts/doctor.sh # Backend and Vite dev server together; Ctrl-C stops both. ARGS go to the # backend. dev: @./scripts/dev.sh $(ARGS) run: go run -ldflags "-X main.Version=$(VERSION)" $(CMD_DIR) # Regenerate the experimental Swagger 2.0 spec in docs/api (pinned swag, not a go.mod dependency). api-docs: go run github.com/swaggo/swag/cmd/swag@v1.16.6 init --generalInfo api_docs.go --dir ./cmd/leafwiki,./internal --parseInternal --requiredByDefault --output ./docs/api --outputTypes json,yaml clean: rm -f $(BINARY_NAME) rm -rf $(RELEASE_DIR) test: go test ./... # Built with the toolchain from go.mod, so the result matches CI. lint: go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION) run # Functions no main or test can reach (#1766). deadcode exits 0 even when it # finds something, so any output fails the target. -test counts test-only # helpers as used. It analyses the host platform only: a function called # solely from another GOOS's files would be reported here. deadcode: @out=$$(go run golang.org/x/tools/cmd/deadcode@$(DEADCODE_VERSION) -test ./...) || exit 1; \ if [ -n "$$out" ]; then echo "$$out"; echo "Unreachable functions found; remove them."; exit 1; fi # Frontend checks, in two halves so CI can run them as parallel jobs # (.github/workflows/frontend.yml); ui-check runs both. ui-deps: cd $(UI_DIR) && npm ci --ignore-scripts ui-lint: ui-deps cd $(UI_DIR) && npm run lint -- --max-warnings=0 && npm run format:check # `npm run build` is the type check (tsc -b). ui-test: ui-deps cd $(UI_DIR) && npm test && npm run build ui-check: ui-lint ui-test # The local subset of the pull-request checks. Not included: the linux/386 # tests, govulncheck, Trivy (the dependency scans, and the image scan, which # `make scan-image` runs locally with Docker and trivy installed), the # install-script test, the e2e lint, the E2E suites and the Dev Container # build (.github/workflows/devcontainer.yml). # CI jobs that keep their own command: test-go (go test with coverage for the # report artifact), lint-go (golangci-lint-action, for PR annotations and its # cache) and the e2e lint (npm in e2e/, which has no Make target). ci: verify lint deadcode test cross ui-check bench: go test -bench=. -benchmem -benchtime=3s ./internal/links/... ./internal/core/revision/... # Build and vet (which also type-checks _test.go files) for each of # CROSS_PLATFORMS, without cgo, like the release builds. cross: @for platform in $(CROSS_PLATFORMS); do \ goos=$${platform%/*}; goarch=$${platform#*/}; \ echo "Building and vetting for $$goos/$$goarch..."; \ CGO_ENABLED=0 GOOS=$$goos GOARCH=$$goarch go build ./... || exit 1; \ CGO_ENABLED=0 GOOS=$$goos GOARCH=$$goarch go vet ./... || exit 1; \ done # Small repository checks that need neither Go nor Node; CI runs the same # target (.github/workflows/verify.yml). verify: ./scripts/check-private-references.sh ./scripts/check-toolchain-versions.sh ./scripts/check-toolchain-versions.test.sh ./scripts/changelog.test.sh # Conventional Commit subjects for BASE..HEAD; CI runs the same script # (.github/workflows/commit-messages.yml). BASE ?= origin/main verify-commits: ./scripts/check-commit-messages.sh "$(BASE)..HEAD" # Build all platform targets release: $(PLATFORMS) @echo "✅ All builds complete." # Build for each platform using Docker $(PLATFORMS): @mkdir -p $(RELEASE_DIR) @GOOS=$(word 1,$(subst /, ,$@)) ; \ GOARCH=$(word 2,$(subst /, ,$@)) ; \ EXT=$$( [ "$$GOOS" = "windows" ] && echo ".exe" || echo "" ) ; \ OUTPUT=$(BINARY_NAME)-$(VERSION)-$$GOOS-$$GOARCH$$EXT ; \ echo "📦 Building $$OUTPUT..." ; \ docker build -f $(DOCKER_BUILDER) \ --build-arg GOOS=$$GOOS \ --build-arg GOARCH=$$GOARCH \ --build-arg APP_VERSION=$(VERSION) \ --build-arg OUTPUT=$(BINARY_NAME) \ -t leafwiki-builder-$$GOOS-$$GOARCH . ; \ ID=$$(docker create leafwiki-builder-$$GOOS-$$GOARCH) ; \ docker cp $$ID:/out/$(BINARY_NAME) $(RELEASE_DIR)/$$OUTPUT ; \ docker rm $$ID ; \ echo "✅ Binary done: $(RELEASE_DIR)/$$OUTPUT" ; \ (cd $(RELEASE_DIR) && sha256sum $$OUTPUT > $$OUTPUT.sha256) ; \ zip -j $(RELEASE_DIR)/$$OUTPUT.zip $(RELEASE_DIR)/$$OUTPUT ; \ tar -czf $(RELEASE_DIR)/$$OUTPUT.tar.gz -C $(RELEASE_DIR) $$OUTPUT ; \ echo "📦 Compressed: zip and tar.gz" # Build inputs of the production image that don't depend on the platform. # docker-build-publish and scan-image share them, so the release scan checks # the image that gets pushed and the push reuses the scan's BuildKit cache. IMAGE_BUILD_FLAGS = --file Dockerfile --target final --build-arg APP_VERSION=$(VERSION) # Trivy settings shared by the image scans: fail only on HIGH/CRITICAL # findings that have a fix; .trivyignore holds the accepted false positives. TRIVY_IMAGE_FLAGS = --severity HIGH,CRITICAL --ignore-unfixed --ignorefile .trivyignore require-trivy: @command -v trivy >/dev/null 2>&1 || { echo "trivy is not installed: https://trivy.dev/latest/getting-started/installation/"; exit 1; } # Build the final image for one platform, load it into Docker and scan it. # CI runs it on PRs that touch the image inputs # (.github/workflows/image-scan.yml) and in the release workflow before the # push, once per platform. SCAN_PLATFORM ?= linux/amd64 SCAN_TAG ?= leafwiki:scan scan-image: require-trivy docker buildx build $(IMAGE_BUILD_FLAGS) --platform $(SCAN_PLATFORM) --load --tag $(SCAN_TAG) . trivy image --image-src docker --platform $(SCAN_PLATFORM) $(TRIVY_IMAGE_FLAGS) \ --exit-code 1 $(SCAN_TAG) # Scan the published image straight from the registry (--image-src remote, so # a stale local copy with another platform isn't scanned instead). The weekly # CI run (.github/workflows/image-scan.yml) calls it twice: once with # TRIVY_EXIT_CODE=0 and SARIF output for code scanning, once as the gate. SCAN_IMAGE ?= ghcr.io/perber/leafwiki:latest TRIVY_EXIT_CODE ?= 1 TRIVY_EXTRA_FLAGS ?= scan-published-image: require-trivy trivy image --image-src remote --platform $(SCAN_PLATFORM) $(TRIVY_IMAGE_FLAGS) \ --exit-code $(TRIVY_EXIT_CODE) $(TRIVY_EXTRA_FLAGS) $(SCAN_IMAGE) # Final production Docker image docker-build-publish: ifndef REPO_OWNER $(error REPO_OWNER is not set. Usage: make docker-build-publish VERSION=vX.Y.Z REPO_OWNER=your_github_username) endif docker buildx build $(IMAGE_BUILD_FLAGS) \ --platform linux/amd64,linux/arm64 \ --tag ghcr.io/$(REPO_OWNER)/leafwiki:$(VERSION) \ --tag ghcr.io/$(REPO_OWNER)/leafwiki:latest \ --annotation "index:org.opencontainers.image.title=LeafWiki" \ --annotation "index:org.opencontainers.image.description=LeafWiki – A fast wiki for people who think in folders, not feeds" \ --sbom=true \ --provenance=mode=max \ --push . # Generate markdown changelog between two tags changelog: @if [ -z "$(PREVIOUS)" ] || [ -z "$(CURRENT)" ]; then \ echo "Usage: make changelog PREVIOUS=v0.1.0 CURRENT=v0.2.0"; \ exit 1; \ fi @./scripts/changelog.sh $(PREVIOUS) $(CURRENT) run-e2e: @echo "🚀 Starting end-to-end tests..." @./e2e/run.sh run-proxy-e2e: @echo "🔐 Starting proxy auth E2E tests..." @./e2e-proxy/run.sh run-e2e-local: @echo "⚡ Starting end-to-end tests (local fast path)..." @E2E_RUN_MODE=local ./e2e/run.sh # Skip the Vite build when dist/ is already up to date; useful when iterating # on tests without changing the frontend. Pass GREP= to filter tests, # e.g.: make run-e2e-local-fast GREP="shoutout" run-e2e-local-fast: @echo "⚡ Starting end-to-end tests (local, skip UI build)..." @E2E_RUN_MODE=local E2E_SKIP_UI_BUILD=1 ./e2e/run.sh $(if $(GREP),--grep "$(GREP)",) help: @echo "Available commands:" @echo " make setup – Install npm dependencies (frontend and e2e)" @echo " make setup-e2e – make setup plus the Playwright browser for local E2E runs" @echo " make doctor – Show Go, Node, npm and mise versions; check them against go.mod and .nvmrc" @echo " make dev – Run backend and Vite dev server together (http://localhost:5173)" @echo " Optional: ARGS=\"\"" @echo " make build – Build self-contained binary with embedded UI (needs Node.js)" @echo " make build-api – Build API-only binary (use with Vite in Dev Setup)" @echo " make ui – Build frontend into internal/http/dist for embedding" @echo " make release – Cross-compile binaries for all platforms (via Docker)" @echo " make clean – Clean all generated files" @echo " make api-docs – Regenerate the experimental Swagger spec in docs/api" @echo " make test – Run all Go tests" @echo " make lint – Run golangci-lint (version from .golangci-lint-version)" @echo " make ui-check – Frontend install, lint, format check, tests and build" @echo " (ui-lint and ui-test run the two halves)" @echo " make ci – verify, lint, deadcode, test, cross and ui-check, like CI" @echo " make deadcode – Fail on Go functions no main or test reaches" @echo " make verify – Run small repository checks (private references, toolchain versions)" @echo " make verify-commits – Check commit subjects since BASE (default origin/main)" @echo " make bench – Run Go benchmarks for links and revision" @echo " make cross – Build and vet for windows/amd64 and darwin/arm64" @echo " Optional: CROSS_PLATFORMS=\"/ ...\"" @echo " make run-e2e – Run end-to-end tests (using Docker)" @echo " make run-proxy-e2e – Run reverse-proxy auth E2E tests (Docker + nginx)" @echo " make run-e2e-local – Run end-to-end tests via local fast path" @echo " make run-e2e-local-fast – Run E2E tests locally, skip UI build (use when dist/ is current)" @echo " Optional: GREP= to filter tests" @echo " make run – Run development server (API only; use Vite for UI)" @echo " make docker-build-publish – Build and push multi-arch Docker image" @echo " make scan-image – Build the Docker image for one platform and scan it with Trivy" @echo " Optional: SCAN_PLATFORM=linux/arm64 SCAN_TAG=" @echo " make scan-published-image – Scan the published image (SCAN_IMAGE, default ghcr.io/perber/leafwiki:latest) with Trivy" @echo " Optional: SCAN_PLATFORM, TRIVY_EXIT_CODE, TRIVY_EXTRA_FLAGS" @echo " make changelog – Generate changelog" .PHONY: all build build-api ui setup setup-e2e doctor dev run api-docs clean test lint deadcode ui-deps ui-lint ui-test ui-check ci bench cross verify verify-commits help docker-build-publish require-trivy scan-image scan-published-image changelog run-e2e run-e2e-local run-e2e-local-fast run-proxy-e2e