Ctrl K
ComunidadGuías…

Makefile

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Caprock — build, test, docs, and release targets.
#
# Go module lives at the repo root; the dashboard lives in ui/ and is embedded into
# the binary from internal/api/dist (built by `make ui`). That directory is
# committed so `go install` embeds a real UI, but only master's dist workflow
# commits it: a pull request never carries it (ADR-068).

DOCS := $(shell find . -name '*.md' \
          -not -path './node_modules/*' -not -path './.git/*' \
          -not -path './vendor/*' -not -path './ui/node_modules/*' \
          -not -path './ui/dist/*' -not -path './.claude/*' \
          -not -path './app/node_modules/*' -not -path './app/src-tauri/target/*' \
          -not -path './relay/node_modules/*' -not -path './relay/.wrangler/*' \
          -not -path './ios-app/node_modules/*' -not -path './ios-app/ios/App/App/public/*' | sort)

# Every recipe below is POSIX shell: pipelines, `[ -d ]`, single-quoted
# -ldflags, `2>/dev/null`. On Windows, GNU make with no `sh` on PATH falls back
# to cmd.exe, which understands none of it — the first Windows contributor to
# clone the repo hit `mkdir -p` on the very first command in CONTRIBUTING.md
# (FB-025).
#
# CI never caught it because CI does not use make on Windows: it runs `go build`
# and `go test` directly, so the Windows job is green while `make build` is
# broken. A green matrix is not the same as a working repository.
#
# Rather than rewrite ~20 recipes in a dialect no one here can test, point make
# at the Bash that is already on every Windows dev machine: Git for Windows
# ships one, and Go and Node are prerequisites anyway. If it is on PATH under
# any of its usual names, use it; otherwise fall back and let the recipe fail
# with a clear message rather than a mangled one.
ifeq ($(OS),Windows_NT)
  SHELL := $(shell where bash 2>NUL | findstr /R "." || echo bash)
  .SHELLFLAGS := -c
  # `go build -o bin/caprock` writes exactly that name, with no extension —
  # `go build` only appends .exe when it is choosing the name itself. The
  # result is a file Windows will not run by name, which is a broken build that
  # exits 0. The CI job added with this fix is what caught it.
  EXE := .exe
endif

BIN      := bin
VERSION  ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev)
LDFLAGS  := -s -w -X github.com/dspv/caprock/internal/version.Version=$(VERSION)
# Mail's sign-in client IDs (docs/mail-oauth.md), from the environment when
# set (release.yml passes the GitHub secrets of the same names); empty, Mail
# offers app passwords instead of the sign-in buttons.
MAIL_OAUTH := github.com/dspv/caprock/internal/mail/oauth
LDFLAGS  += -X $(MAIL_OAUTH).GoogleClientID=$(CAPROCK_MAIL_GOOGLE_CLIENT_ID) -X $(MAIL_OAUTH).GoogleClientSecret=$(CAPROCK_MAIL_GOOGLE_CLIENT_SECRET) -X $(MAIL_OAUTH).MicrosoftClientID=$(CAPROCK_MAIL_MICROSOFT_CLIENT_ID)
GOFLAGS  := -trimpath
export CGO_ENABLED = 0

.DEFAULT_GOAL := help

# --- help -----------------------------------------------------------------
.PHONY: help
help: ## Show this help
	@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
	  | sort | awk 'BEGIN{FS=":.*?## "}{printf "  \033[36m%-14s\033[0m %s\n", $$1, $$2}'

# --- build ----------------------------------------------------------------
.PHONY: build
build: ui ## Build ./bin/caprock, ./bin/caprock-hook and ./bin/caprock-pty-host (with the embedded UI)
	@mkdir -p $(BIN)
	go build $(GOFLAGS) -ldflags '$(LDFLAGS)' -o $(BIN)/caprock$(EXE) ./cmd/caprock
	go build $(GOFLAGS) -ldflags '$(LDFLAGS)' -o $(BIN)/caprock-hook$(EXE) ./cmd/caprock-hook
	go build $(GOFLAGS) -ldflags '$(LDFLAGS)' -o $(BIN)/caprock-pty-host$(EXE) ./cmd/caprock-pty-host

.PHONY: build-go
build-go: ## Build the Go binaries only (uses whatever UI is already embedded)
	@mkdir -p $(BIN)
	go build $(GOFLAGS) -ldflags '$(LDFLAGS)' -o $(BIN)/caprock$(EXE) ./cmd/caprock
	go build $(GOFLAGS) -ldflags '$(LDFLAGS)' -o $(BIN)/caprock-hook$(EXE) ./cmd/caprock-hook
	go build $(GOFLAGS) -ldflags '$(LDFLAGS)' -o $(BIN)/caprock-pty-host$(EXE) ./cmd/caprock-pty-host

.PHONY: ui
ui: ## Build the dashboard into internal/api/dist (installs deps on a fresh clone; never commit the result)
	cd ui && { [ -d node_modules ] || npm ci; }
	cd ui && npm run build

# internal/api/dist is committed for `go install`, and rebuilt on master by
# .github/workflows/dist.yml after every merge that touches ui/ (ADR-068). A
# branch never changes it: two branches that both rebuilt it always conflicted
# there, whatever else they touched. A local `make ui` leaves the directory
# modified; that is expected, and nothing here asks you to commit it.

.PHONY: dist-untouched
dist-untouched: ## Fail if this branch's commits change internal/api/dist (master rebuilds it; ADR-068)
	@base=$$(git merge-base HEAD origin/master 2>/dev/null) || { \
		echo "dist-untouched: no origin/master here, skipped"; exit 0; }; \
	if ! git diff --quiet "$$base" HEAD -- internal/api/dist; then \
		echo "This branch changes internal/api/dist. dist is rebuilt on master after merge; drop your dist changes:"; \
		echo "  make dist-reset && git commit -m 'chore: drop the dist changes'"; \
		git --no-pager diff --stat "$$base" HEAD -- internal/api/dist | tail -3; exit 1; fi
	@echo "dist is untouched by this branch"

.PHONY: dist-reset
dist-reset: ## Put internal/api/dist back as this branch's base on master had it (staged; commit to drop a branch's dist changes)
	@base=$$(git merge-base HEAD origin/master) && \
	git rm -rq --cached --ignore-unmatch -- internal/api/dist && \
	git checkout "$$base" -- internal/api/dist && \
	git clean -qfd -- internal/api/dist && \
	echo "internal/api/dist is as $$(git rev-parse --short $$base) has it (staged)"

.PHONY: dist-check
dist-check: ui ## Fail if the committed internal/api/dist/ is not this commit's ui/ built (the release gate for `go install`)
	@git status --porcelain --untracked-files=all -- internal/api/dist \
		| grep -vE '^[MARD] ' | grep -q . && { \
		echo "internal/api/dist/ is not ui/ built. Releases tag the commit after master's dist rebuild:"; \
		echo "  wait for the dist workflow (gh run list -w dist -L 3), git pull, then tag; never commit dist by hand"; \
		git --no-pager status --short -- internal/api/dist | tail -5; exit 1; } || true
	@echo "dist is in sync with ui/"

.PHONY: ui-install
ui-install: ## npm ci for the dashboard
	cd ui && npm ci

.PHONY: dev
dev: ## Run daemon (go run) + vite dev server; UI on :5173 proxies API to :22776
	@./scripts/dev.sh

# --- test / lint ----------------------------------------------------------
.PHONY: test
test: test-go test-ui test-scripts ## All tests

.PHONY: test-go
test-go: ## Go tests
	go test ./...

# Every statement the Go tests run, explained against the latest schema
# (internal/store/plans_test.go). The OpenCode database on this machine is
# left out, as on CI, where the check runs on the go job's own test run.
PLANS_DIR := bin/plans

.PHONY: plans
plans: ## Rewrite internal/store/testdata/query-plans.txt from a run of the Go tests; review its diff
	rm -rf $(PLANS_DIR)
	CAPROCK_PLAN_LOG=$(CURDIR)/$(PLANS_DIR) OPENCODE_DB=:memory: go test -count=1 ./...
	CAPROCK_PLANS_FROM=$(CURDIR)/$(PLANS_DIR) CAPROCK_PLANS_UPDATE=1 go test -count=1 -run '^TestQueryPlans$$' ./internal/store

.PHONY: test-scripts
test-scripts: ## Release script tests (latest.json for the app updater, changelog fragments)
	python3 -m unittest discover -s scripts -p 'test_*.py'

.PHONY: test-ui
test-ui: ## Dashboard tests
	cd ui && npm test -- --run

.PHONY: lint
lint: lint-go lint-ui ## All linters

.PHONY: lint-go
lint-go: ## go vet + golangci-lint
	go vet ./...
	golangci-lint run

.PHONY: lint-ui
lint-ui: ## tsc --noEmit
	cd ui && npm run typecheck

.PHONY: smoke
smoke: build-go ## Phase 0 DoD scenario + the Phase 2 e2e (what CI's smoke step runs)
	go test -tags smoke -count=1 ./internal/smoke/... ./internal/board/...

# The app's golden paths in a headless browser (ui/e2e, .ai/10-infrastructure.md
# § E2E): this checkout's dashboard, built into a temp directory and served by
# `vite preview`, in front of a daemon under `env -i` with a temp HOME and data
# dir, a free port and testdata/fakeclaude as its claude. The browser is
# Playwright's own headless shell, never the system Chrome. About 20 s.
.PHONY: e2e
e2e: build-go ## The app's golden paths in Playwright's headless shell against a sandboxed daemon (ui/e2e)
	cd ui && { [ -d node_modules ] || npm ci; }
	cd ui && node e2e/has-browser.mjs
	cd ui && npx playwright test -c e2e $(E2E_ARGS)

# --- desktop app (app/, Tauri v2; ADR-038) ----------------------------------
# The app bundles the daemon as a Tauri sidecar, which Tauri looks for as
# app/src-tauri/binaries/caprock-<rust host triple>[.exe]; app-sidecar builds
# it from this checkout so the app always carries the daemon it was built with,
# and caprock-pty-host beside it, the binary the daemon holds terminals in.
APP_SRC    := app/src-tauri
APP_TRIPLE  = $(shell rustc -vV 2>/dev/null | sed -n 's/^host: //p')
# Bundle formats for app-bundle; empty means every format tauri.conf.json lists
# for this OS. `make app-bundle APP_BUNDLES=app` skips Tauri's macOS .dmg, whose
# layout step drives Finder (releases and CI make theirs with scripts/app-dmg.sh).
APP_BUNDLES ?=
# The app's version for app-bundle (release jobs pass the tag without the v);
# empty keeps tauri.conf.json's.
APP_VERSION ?=
# With TAURI_SIGNING_PRIVATE_KEY (and _PASSWORD) in the environment,
# app-bundle also writes the signed updater bundles (F20, ADR-042):
# tauri.updater.conf.json turns createUpdaterArtifacts on. Without the key
# it builds exactly as before.

.PHONY: app-sidecar
app-sidecar: ## Build the daemon the app bundles (app/src-tauri/binaries/caprock[-pty-host]-<triple>)
	@[ -n "$(APP_TRIPLE)" ] || { echo "rustc not found: install Rust with rustup (see app/README.md)"; exit 1; }
	@mkdir -p $(APP_SRC)/binaries
	go build $(GOFLAGS) -ldflags '$(LDFLAGS)' -o $(APP_SRC)/binaries/caprock-$(APP_TRIPLE)$(EXE) ./cmd/caprock
	go build $(GOFLAGS) -ldflags '$(LDFLAGS)' -o $(APP_SRC)/binaries/caprock-pty-host-$(APP_TRIPLE)$(EXE) ./cmd/caprock-pty-host

.PHONY: app
app: app-sidecar ## Run the desktop app in development (debug build; finds or starts a daemon)
	cargo run --manifest-path $(APP_SRC)/Cargo.toml

.PHONY: app-test
app-test: app-sidecar ## Desktop app: cargo fmt --check, clippy -D warnings, cargo test
	cargo fmt --manifest-path $(APP_SRC)/Cargo.toml --check
	cargo clippy --manifest-path $(APP_SRC)/Cargo.toml --all-targets -- -D warnings
	cargo test --manifest-path $(APP_SRC)/Cargo.toml

.PHONY: app-bundle
app-bundle: app-sidecar ## Build the release app bundle(s) under app/src-tauri/target/release/bundle
	cd app && { [ -d node_modules ] || npm ci; }
	cd app && npx tauri build $(if $(APP_BUNDLES),--bundles $(APP_BUNDLES),) $(if $(APP_VERSION),--config '{"version":"$(APP_VERSION)"}',) $(if $(TAURI_SIGNING_PRIVATE_KEY),--config src-tauri/tauri.updater.conf.json,)

.PHONY: app-local
app-local: ## macOS: build this checkout's app (host arch, release) and install it over /Applications/Caprock.app
	@bash scripts/app-local.sh

.PHONY: app-local-revert
app-local-revert: ## macOS: back from a local build to the released app (brew reinstall --cask dspv/tap/caprock-app)
	@bash scripts/app-local.sh --revert

.PHONY: app-release
app-release: ## macOS: build the universal .dmg for TAG=vX.Y.Z, check it, attach it to the release, render the cask
	@[ -n "$(TAG)" ] || { echo "usage: make app-release TAG=vX.Y.Z [ARGS='--no-upload --clobber --cask-pr']"; exit 1; }
	@bash scripts/app-release.sh $(TAG) $(ARGS)

.PHONY: app-latest
app-latest: ## Attach version-less copies of TAG=vX.Y.Z's app files, add them to checksums.txt, mark it Latest
	@[ -n "$(TAG)" ] || { echo "usage: make app-latest TAG=vX.Y.Z [ARGS=--allow-missing]"; exit 1; }
	@bash scripts/app-latest.sh $(TAG) $(ARGS)

# --- docs -----------------------------------------------------------------
.PHONY: docs-fmt
docs-fmt: ## Tight-align all markdown tables (run after editing any table)
	@python3 scripts/align-tables.py $(DOCS)

.PHONY: docs-check
docs-check: ## Fail if any markdown table is unaligned or a changelog fragment is malformed (CI gate)
	@python3 scripts/align-tables.py --check $(DOCS)
	@python3 scripts/changelog.py check

.PHONY: docs-links
docs-links: ## Fail if any relative markdown link does not resolve
	@python3 scripts/check-links.py $(DOCS)

.PHONY: lang-check
lang-check: ## Fail if any tracked text file holds Cyrillic (generated dist and binaries skipped)
	@python3 scripts/check-no-cyrillic.py

.PHONY: pr-check
pr-check: ## Before opening a PR: changed Go packages vet + test -race -count=3, Windows vet + test compile, the whole UI suite when ui/ changed, make e2e when ui/ or internal/api changed
	@./scripts/pr-check.sh

.PHONY: check
check: check-go check-ui ## Docs gates + lint + test + dist untouched + smoke (what CI runs, minus the OS matrix)

# The two halves of check, which share nothing, so release.yml's verify runs
# them as two jobs side by side. Together they are exactly check.
.PHONY: check-go
check-go: docs-check docs-links lang-check lint-go test-go smoke ## check's Go half: docs gates, vet + golangci-lint, go test, smoke

.PHONY: check-ui
check-ui: lint-ui test-ui test-scripts dist-untouched ## check's dashboard half: typecheck, vitest, release script tests, dist untouched

.PHONY: release-ready
release-ready: check dist-check ## Everything `check` does, plus the things only a release forgets
	@# dist-check: the tag must hold its own ui/ built, or `go install @vX.Y.Z`
	@# embeds an older dashboard; release.yml's verify fails the same way, but
	@# only after the tag is pushed, and a tag is never moved.
	@echo ""
	@echo "→ the site's changelog"
	@# The site keeps its own copy of CHANGELOG.md, regenerated by a script in
	@# that repository. Nothing enforces running it, so a release cycle that
	@# skips the step leaves caprock.dev showing a version that no longer
	@# exists — which is what happened on 0.49.1 through 0.49.3, and was only
	@# noticed because a stray background job was still watching for a string.
	@#
	@# A warning rather than a failure: the site is a sibling checkout that not
	@# everyone building this repo has, and a gate that cannot run everywhere is
	@# a gate people learn to skip.
	@if [ -d ../caprock-web ]; then \
		if ! (cd ../caprock-web && node scripts/sync-changelog.mjs --check >/dev/null 2>&1); then \
			echo "  ✗ caprock.dev is behind CHANGELOG.md."; \
			echo "    cd ../caprock-web && npm run sync:changelog && git commit -am 'docs(changelog): sync' && git push"; \
			exit 1; \
		fi; \
		echo "  ✓ the site's changelog matches"; \
	else \
		echo "  – no ../caprock-web checkout; sync it wherever you keep it"; \
	fi
	@echo ""
	@echo "Ready. Tag with: git tag -a vX.Y.Z -m '...' && git push origin vX.Y.Z"

.PHONY: shots
shots: ## Re-take the documented screenshots and open a PR (needs a real database)
	@bash scripts/refresh-shots.sh

.PHONY: record
record: ## Serve a scrubbed dashboard on :4291 to record video from (Ctrl-C to stop)
	@bash scripts/record-stand.sh

.PHONY: gif
gif: ## Re-record a README GIF from the stand (make record first). SCENE=context-tax
	@python3 scripts/record-gif.py $(or $(SCENE),context-tax)

.PHONY: reload
reload: build ## Build the dashboard + binaries and restart the running daemon on this machine
	@# Runs ./bin/caprock rather than installing over the copy on PATH.
	@#
	@# It used to `install` over whatever `command -v caprock` found, which on a
	@# Homebrew machine replaces brew's symlink with a plain file: `brew upgrade`
	@# then reports success while PATH still resolves to the dev build, and the
	@# next `brew doctor` complains. Exactly the breakage the update dialog warns
	@# about, caused by our own dev loop. Recovering meant `brew link --overwrite`.
	@$(BIN)/caprock down >/dev/null 2>&1 || true
	@$(BIN)/caprock up --no-open >/dev/null 2>&1 || true
	@echo "reloaded → $$($(BIN)/caprock --version) (from ./bin; the copy on PATH is untouched)"