-
Notifications
You must be signed in to change notification settings - Fork 160
Expand file tree
/
Copy pathMakefile
More file actions
558 lines (483 loc) · 26.5 KB
/
Copy pathMakefile
File metadata and controls
558 lines (483 loc) · 26.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
# Makefile for msgvault
.DEFAULT_GOAL := help
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")
LDFLAGS := -X go.kenn.io/msgvault/cmd/msgvault/cmd.Version=$(VERSION) \
-X go.kenn.io/msgvault/cmd/msgvault/cmd.Commit=$(COMMIT) \
-X go.kenn.io/msgvault/cmd/msgvault/cmd.BuildDate=$(BUILD_DATE)
LDFLAGS_RELEASE := $(LDFLAGS) -s -w
# Default build tags applied to every go build/test/bench invocation.
# - fts5: enable the SQLite FTS5 full-text search extension
# - sqlite_vec: enable the sqlite-vec extension for vector search
BUILD_TAGS := fts5 sqlite_vec
TEST_TIMEOUT := 60m
# Cap on test binaries the PostgreSQL lanes run at once. go test defaults -p
# to the host CPU count, and every PostgreSQL-backed test binary opens its own
# connections: an admin handle of three (one pinned for the life of the binary
# to hold its template database's ownership lock, see
# internal/testutil/pg_template.go) plus the store under test. Nothing budgets
# across binaries, so on a wide runner `go test ./...` starts every PostgreSQL
# package together and the sum exceeds a stock server's 100 connections
# ("sorry, too many clients already") and its lock table ("out of shared
# memory"). Four is the GitHub-hosted profile these lanes were tuned on. The
# pgvector lane in .github/workflows/ci.yml carries the same value inline.
PG_TEST_PARALLEL ?= 4
# Packages whose tests CI runs as shards in their own jobs
# (scripts/test-package-shards.sh): each is a few thousand tests that run one
# at a time inside one binary, so left whole it sets the wall clock of the
# whole lane. The *-unsharded targets run everything else and exist for CI;
# `make test` and `make test-pg-shipped` still run every package.
SHARDED_TEST_PKGS := ./cmd/msgvault/cmd ./internal/store ./internal/api
TEST_SHARDS ?= 4
TEST_PROFILE ?= auto
# Only the automatic SQLite profile adds query and overlaps package jobs.
SQLITE_SHARDED_TEST_PKGS := $(sort $(SHARDED_TEST_PKGS) ./internal/query)
SQLITE_SHARD_TARGETS := $(addprefix test-sqlite-shard/,$(SQLITE_SHARDED_TEST_PKGS))
GOLANGCI_LINT_VERSION ?= v2.13.1
GOVULNCHECK_VERSION ?= v1.7.0
HUMA_CHECK_VERSION := 3e1f59e9011e878ec595aa04aebc8a77c5292c4d
GO_INSTALL_BIN := $(shell go env GOBIN)
ifeq ($(strip $(GO_INSTALL_BIN)),)
GO_INSTALL_BIN := $(shell go env GOPATH)/bin
endif
GOLANGCI_LINT_BIN := $(GO_INSTALL_BIN)/golangci-lint
CI_TOOLS_BIN := $(shell git rev-parse --path-format=absolute --git-path ci-tools/bin)
CUSTOM_GCL_BIN := $(CI_TOOLS_BIN)/custom-gcl$(shell go env GOEXE)
GOVULNCHECK_BIN := $(CI_TOOLS_BIN)/govulncheck
# Build tags for the PostgreSQL test lane (test-pg). Must be the full build set:
# pgvector gates the vector-on-PG code paths (//go:build pgvector), and sqlite_vec
# is required too because several tests are gated on BOTH tags
# (//go:build sqlite_vec && pgvector) — the pgvector<->sqlitevec parity test
# (internal/vector/pgvector/parity_test.go) and the PG command-wiring tests
# (cmd/msgvault/cmd/{serve_vector_pg,embed_pg,search_vector_pg,embed_vector_pg}_test.go).
# Omitting sqlite_vec compiles those out and the target gives false confidence.
PG_TEST_TAGS := fts5 sqlite_vec pgvector
# The only packages that build a different test binary under BUILD_TAGS than
# under PG_TEST_TAGS. That is not just the packages carrying pgvector-gated
# files: a package whose own sources are identical still links different code
# when something in its dependency closure changed, so this is the reverse
# dependency closure of the tag-sensitive packages, not the tag-sensitive
# packages themselves. Every package outside this set compiles byte-identically
# in both configurations, so test-pg-both runs just these in the shipped-build
# configuration. Verified by `make pg-shipped-only-check`, which re-derives the
# closure from `go list`.
PG_SHIPPED_ONLY_PKGS := ./cmd/msgvault ./cmd/msgvault/cmd ./internal/api ./internal/daemonclient ./internal/mcp ./internal/scheduler ./internal/store ./internal/vector/chunkmatch ./internal/vector/document ./internal/vector/embed ./internal/vector/hybrid ./internal/vector/pgvector ./scripts/contextual-retrieval-eval
OPENAPI_ARTIFACTS := api/openapi.yaml pkg/client/openapi.yaml pkg/client/generated
WEB_INSTALL_STAMP := web/node_modules/.msgvault-install-stamp
# Keep golangci-lint results scoped to this git worktree. Its cache can contain
# absolute source paths, so sharing the default user cache across worktrees can
# replay diagnostics for deleted worktree paths.
DEFAULT_GOLANGCI_LINT_CACHE := $(shell git rev-parse --path-format=absolute --git-path golangci-lint-cache)
GOLANGCI_LINT_CACHE ?= $(DEFAULT_GOLANGCI_LINT_CACHE)
export GOLANGCI_LINT_CACHE
# golangci-lint's runner lock lives under os.TempDir(), independently of its
# analysis cache. Keep that lock worktree-local too, so linked worktrees do not
# serialize one another while duplicate runners in one worktree can wait.
GOLANGCI_LINT_TMP ?= $(GOLANGCI_LINT_CACHE)/tmp
.PHONY: build build-release install clean test test-unsharded test-shards test-v test-pg test-pg-shipped test-pg-shipped-unsharded test-pg-both pg-shipped-only-check require-test-db fmt lint-tools custom-gcl lint lint-ci vuln-tools vulncheck testify-helper-check tidy openapi api-generate openapi-check api-check web-install web-generate web-check web-test web-test-browser web-e2e web-build web-embed web-assets-check smoke-web-release shootout run-shootout install-hooks bench vcard-registry-check vcard-registry-update docs-install docs-build docs-serve docs-check docs-fixture-test docs-fixture-check docs-fixture-smoke docs-web-screenshots docs-screenshots docs-assets-branch docs-generated-assets-branch docs-deploy-staging docs-deploy help
# Build the binary (debug)
build: web-embed
ifeq ($(shell go env GOOS),linux)
CGO_ENABLED=0 go build -trimpath -buildvcs=false -o msgvault-codex-bridge ./cmd/msgvault-codex-bridge
chmod 755 msgvault-codex-bridge
@bridge_digest=$$(sha256sum msgvault-codex-bridge | cut -d' ' -f1); \
CGO_ENABLED=1 go build -tags "$(BUILD_TAGS)" -ldflags="$(LDFLAGS) -X go.kenn.io/msgvault/internal/peoplesweep.codexBridgeSHA256=$$bridge_digest" -o msgvault ./cmd/msgvault
else
CGO_ENABLED=1 go build -tags "$(BUILD_TAGS)" -ldflags="$(LDFLAGS)" -o msgvault ./cmd/msgvault
endif
@chmod +x msgvault
# Build with optimizations (release)
build-release: web-embed
ifeq ($(shell go env GOOS),linux)
CGO_ENABLED=0 go build -trimpath -buildvcs=false -o msgvault-codex-bridge ./cmd/msgvault-codex-bridge
chmod 755 msgvault-codex-bridge
@bridge_digest=$$(sha256sum msgvault-codex-bridge | cut -d' ' -f1); \
CGO_ENABLED=1 go build -tags "$(BUILD_TAGS)" -ldflags="$(LDFLAGS_RELEASE) -X go.kenn.io/msgvault/internal/peoplesweep.codexBridgeSHA256=$$bridge_digest" -trimpath -o msgvault ./cmd/msgvault
else
CGO_ENABLED=1 go build -tags "$(BUILD_TAGS)" -ldflags="$(LDFLAGS_RELEASE)" -trimpath -o msgvault ./cmd/msgvault
endif
@chmod +x msgvault
# Install to ~/.local/bin, $GOBIN, or $GOPATH/bin
install: build
@set -e; if [ -d "$(HOME)/.local/bin" ]; then \
echo "Installing to ~/.local/bin/msgvault"; \
install -m 755 msgvault "$(HOME)/.local/bin/msgvault"; \
if [ "$$(go env GOOS)" = linux ]; then install -m 755 msgvault-codex-bridge "$(HOME)/.local/bin/msgvault-codex-bridge"; fi; \
else \
INSTALL_DIR="$${GOBIN:-$$(go env GOBIN)}"; \
if [ -z "$$INSTALL_DIR" ]; then \
GOPATH_FIRST="$$(go env GOPATH | cut -d: -f1)"; \
INSTALL_DIR="$$GOPATH_FIRST/bin"; \
fi; \
mkdir -p "$$INSTALL_DIR"; \
echo "Installing to $$INSTALL_DIR/msgvault"; \
install -m 755 msgvault "$$INSTALL_DIR/msgvault"; \
if [ "$$(go env GOOS)" = linux ]; then install -m 755 msgvault-codex-bridge "$$INSTALL_DIR/msgvault-codex-bridge"; fi; \
fi
# Clean build artifacts
clean:
rm -f msgvault msgvault.exe msgvault-codex-bridge mimeshootout
rm -rf bin/
# Scale the SQLite suite when both CPU and memory budgets allow it. An explicit
# TEST_SHARDS, a database URL, or TEST_PROFILE=standard keeps the existing layout.
test:
@case "$(TEST_PROFILE)" in auto|standard) ;; *) echo "TEST_PROFILE must be auto or standard" >&2; exit 1 ;; esac; \
shards=0; \
if [ "$(TEST_PROFILE)" = auto ] && [ "$(origin TEST_SHARDS)" = file ] && [ -z "$(MSGVAULT_TEST_DB)" ]; then \
profile=$$(go run ./scripts/test-resources -packages $(words $(SQLITE_SHARDED_TEST_PKGS))) || exit $$?; \
set -- $$profile; shards=$$1; \
fi; \
if [ "$$shards" -gt 0 ]; then \
echo "SQLite tests: concurrent packages, $$shards shards per large package"; \
$(MAKE) -j$$(($(words $(SQLITE_SHARDED_TEST_PKGS)) + 1)) GOMAXPROCS=$$2 GOFLAGS="$(GOFLAGS) -p=1" \
test-unsharded $(SQLITE_SHARD_TARGETS) SHARDED_TEST_PKGS="$(SQLITE_SHARDED_TEST_PKGS)" \
TEST_SHARDS=$$shards TEST_REMAINDER_PARALLEL=$$3; \
else \
echo "Tests: standard package schedule, $(TEST_SHARDS) shards"; \
$(MAKE) test-standard; \
fi
.PHONY: test-standard $(SQLITE_SHARD_TARGETS)
test-standard:
$(MAKE) test-unsharded
$(MAKE) test-shards
# These explicit goals let make own job waiting and error propagation. The
# regular test-shards target remains sequential, including PostgreSQL callers.
$(SQLITE_SHARD_TARGETS): test-sqlite-shard/%:
scripts/test-package-shards.sh $* $(TEST_SHARDS) "$(BUILD_TAGS)" $(TEST_TIMEOUT)
# Everything except SHARDED_TEST_PKGS. CI's test lane runs this alongside the
# sharded jobs; together they cover exactly what `make test` covers.
test-unsharded:
@excluded=$$(go list $(SHARDED_TEST_PKGS) | sed 's/^/-e /' | tr '\n' ' '); \
go test -timeout $(TEST_TIMEOUT) $(if $(TEST_REMAINDER_PARALLEL),-p $(TEST_REMAINDER_PARALLEL),) -tags "$(BUILD_TAGS)" $$(go list ./... | grep -vxF $$excluded)
# SHARDED_TEST_PKGS, each as TEST_SHARDS concurrent processes. Same binary,
# same tests, same per-package timeout; only the process boundary is new.
test-shards:
@for pkg in $(SHARDED_TEST_PKGS); do \
scripts/test-package-shards.sh $$pkg $(TEST_SHARDS) "$(BUILD_TAGS)" $(TEST_TIMEOUT) || exit 1; \
done
# Run tests with verbose output
test-v:
go test -timeout $(TEST_TIMEOUT) -tags "$(BUILD_TAGS)" -v ./...
# Run tests against PostgreSQL with the pgvector tag (set MSGVAULT_TEST_DB
# first). Needs a server with the vector extension available.
# Example: MSGVAULT_TEST_DB=postgres://user:pass@localhost:5432/db make test-pg
#
# CI does not run this target as-is: .github/workflows/ci.yml splits the same
# ground into test-pgvector (pgvector image, pgvector-tagged packages) and
# test-postgres (stock image, test-pg-shipped below).
# See docs/internal/PG_STATUS.md for the supported feature surface.
test-pg: require-test-db
go test -timeout $(TEST_TIMEOUT) -p $(PG_TEST_PARALLEL) -tags "$(PG_TEST_TAGS)" ./...
# Run the SHIPPED build's tests against PostgreSQL (set MSGVAULT_TEST_DB first).
# The released binary is built with BUILD_TAGS and no pgvector, so that build
# has to be exercised against a PostgreSQL archive too. This is the lane
# .github/workflows/ci.yml's test-postgres job runs.
#
# Run the unsharded remainder first, then the large packages as shards. The
# steps stay sequential here so at most TEST_SHARDS test processes share the
# configured PostgreSQL server; CI gives each package its own job and server.
test-pg-shipped: require-test-db
$(MAKE) test-pg-shipped-unsharded
$(MAKE) test-shards
# test-pg-shipped minus SHARDED_TEST_PKGS, for CI's test-postgres lane; the
# test-postgres-sharded jobs cover the rest against their own servers.
test-pg-shipped-unsharded: require-test-db
@excluded=$$(go list $(SHARDED_TEST_PKGS) | sed 's/^/-e /' | tr '\n' ' '); \
go test -timeout $(TEST_TIMEOUT) -p $(PG_TEST_PARALLEL) -tags "$(BUILD_TAGS)" $$(go list ./... | grep -vxF $$excluded)
# Both PostgreSQL lanes' coverage in one pass.
#
# test-pg and test-pg-shipped differ only in the pgvector build tag, and that
# tag changes the test binary of just the packages in PG_SHIPPED_ONLY_PKGS. For
# every other package the two lanes compile a byte-identical test binary and run
# it against the same server with the same environment, so running both in full
# repeats roughly 1000s of work per round. This target runs test-pg in full and
# then only the packages the tag actually changes.
#
# Use this instead of running the two lanes back to back. Do NOT run
# test-pg-shipped's narrow half on its own and call PostgreSQL covered — the
# equivalence argument depends on the full pgvector lane having run on the same
# tree. pg-shipped-only-check re-derives the package set and fails if it drifts.
test-pg-both: require-test-db pg-shipped-only-check
go test -timeout $(TEST_TIMEOUT) -p $(PG_TEST_PARALLEL) -tags "$(PG_TEST_TAGS)" ./...
go test -timeout $(TEST_TIMEOUT) -p $(PG_TEST_PARALLEL) -tags "$(BUILD_TAGS)" $(PG_SHIPPED_ONLY_PKGS)
# Fail if the set of packages whose test binary changes when the pgvector tag is
# dropped no longer matches PG_SHIPPED_ONLY_PKGS. This is the assumption
# test-pg-both rests on, so it is checked rather than trusted: a new
# pgvector-gated file, or a new import of a package that has one, would
# otherwise silently stop being covered in the shipped-build configuration.
#
# Two steps, because a package's own source list is not enough. The first finds
# the packages the tag changes directly. The second walks the test dependency
# graph and adds every package that links one of them, since its test binary
# differs even though its own files do not.
pg-shipped-only-check:
@set -e; \
module="$$(go list -m)"; \
tmp="$$(mktemp -d)"; \
trap 'rm -rf "$$tmp"' EXIT; \
fmt='{{.ImportPath}}|{{.GoFiles}}|{{.TestGoFiles}}|{{.XTestGoFiles}}|{{.CgoFiles}}'; \
go list -deps -test -tags "$(BUILD_TAGS)" -f "$$fmt" ./... | sort > "$$tmp/shipped"; \
go list -deps -test -tags "$(PG_TEST_TAGS)" -f "$$fmt" ./... | sort > "$$tmp/pgvector"; \
diff "$$tmp/shipped" "$$tmp/pgvector" \
| sed -n 's/^[<>] \([^|]*\)|.*/\1/p' \
| sed 's/ \[.*//; s/\.test$$//' \
| sort -u > "$$tmp/sensitive"; \
go list -test -tags "$(BUILD_TAGS)" -f '{{.ImportPath}}|{{join .Deps " "}}' ./... > "$$tmp/deps"; \
awk -v mod="$$module" -F'|' 'NR==FNR { sensitive[$$0]=1; next } { \
name = $$1; \
sub(/ \[.*/, "", name); sub(/\.test$$/, "", name); sub(/_test$$/, "", name); \
if (index(name, mod) != 1) next; \
hit = (name in sensitive); \
if (!hit) { n = split($$2, d, " "); for (i = 1; i <= n && !hit; i++) if (d[i] in sensitive) hit = 1 } \
if (hit) print name \
}' "$$tmp/sensitive" "$$tmp/deps" \
| sed "s|^$$module/|./|; s|^$$module$$|.|" \
| sort -u > "$$tmp/actual"; \
printf '%s\n' $(PG_SHIPPED_ONLY_PKGS) | sort -u > "$$tmp/expected"; \
if ! diff -u "$$tmp/expected" "$$tmp/actual"; then \
echo "PG_SHIPPED_ONLY_PKGS is stale ('-' expected, '+' actual). Update it in the Makefile; test-pg-both's coverage argument depends on it." >&2; \
exit 1; \
fi
require-test-db:
@if [ -z "$$MSGVAULT_TEST_DB" ]; then \
echo "MSGVAULT_TEST_DB must be set, e.g., postgres://user:pass@localhost:5432/db" >&2; \
exit 1; \
fi
# Network-check or update the vendored IANA vCard Elements registry. These are
# manual targets; CI validates handling coverage against the vendored snapshot.
vcard-registry-check:
go run ./internal/vcard/cmd/update-registry
vcard-registry-update:
go run ./internal/vcard/cmd/update-registry --write
# Regenerate the committed OpenAPI schemas and generated Go client.
# api/openapi.yaml is the published OpenAPI 3.1 schema; pkg/client/openapi.yaml
# is the OpenAPI 3.0 schema used by the Go client generator.
# The tools module pins the generator and its checksums independently of the
# client runtime, so generation does not resolve an unchecked dependency graph.
api-generate:
@mkdir -p api pkg/client/generated
set -e; tmp="$$(mktemp)"; trap 'rm -f "$$tmp"' EXIT; go run ./cmd/msgvault openapi > "$$tmp"; if [ -f api/openapi.yaml ] && cmp -s "$$tmp" api/openapi.yaml; then rm "$$tmp"; else mv "$$tmp" api/openapi.yaml; fi; trap - EXIT
set -e; tmp="$$(mktemp)"; trap 'rm -f "$$tmp"' EXIT; go run ./cmd/msgvault openapi --version 3.0 --format yaml > "$$tmp"; if [ -f pkg/client/openapi.yaml ] && cmp -s "$$tmp" pkg/client/openapi.yaml; then rm "$$tmp"; else mv "$$tmp" pkg/client/openapi.yaml; fi; trap - EXIT
cd pkg/client/generated && find . -maxdepth 1 -type f -name '*.go' ! -name 'generate.go' -delete && go tool -modfile=../../../tools/oapi-codegen/go.mod oapi-codegen -config config.yaml ../openapi.yaml
go run ./internal/codegenfix/cmd pkg/client/generated/types.go pkg/client/generated/client.go
openapi-check: api-generate
@git diff --exit-code -- $(OPENAPI_ARTIFACTS) || (echo "OpenAPI generated assets are stale; run 'make api-generate' and commit the changes." >&2; exit 1)
@if [ -n "$$(git status --porcelain --untracked-files=all -- $(OPENAPI_ARTIFACTS))" ]; then \
git status --short --untracked-files=all -- $(OPENAPI_ARTIFACTS); \
echo "OpenAPI generated assets are stale; run 'make api-generate' and commit the changes." >&2; \
exit 1; \
fi
api-check: openapi-check
openapi: api-generate
# Install, generate, validate, test, and build the browser application. Web
# generation is intentionally separate from the Go-only OpenAPI targets so
# API client checks remain runnable on systems without Bun.
web-install: $(WEB_INSTALL_STAMP)
$(WEB_INSTALL_STAMP): web/package.json web/bun.lock
cd web && bun install --frozen-lockfile
@touch $(WEB_INSTALL_STAMP)
web-generate: web-install
cd web && bun run generate
web-check: web-install
cd web && bun run check:generated
cd web && bun run check
cd web && bun run check:kit-ui
web-test:
cd web && bun run test
# test:browser runs everything under web/tests, including tests/e2e specs
# whose fixtures spawn the repo-root msgvault daemon, so the binary must
# exist before Playwright starts. Same invariant as web-e2e below.
web-test-browser: build
cd web && bun run test:browser
# Browser gates use the same digest-pinned Playwright environment as CI.
# Build the real daemon and embedded UI before Playwright test timeouts start.
web-e2e: build
cd web && bun run test:e2e
web-build: web-generate
cd web && bun run build
# Replace prior generated output while preserving the compilation stub, copy
# Vite's complete production distribution into the Go embed tree, then validate
# the staged embed. Validation is a mandatory part of embedding: the tree is
# served without authentication, so every build that embeds it must reject
# hidden files, credential-pattern names, and untracked assets.
web-embed: web-build
@mkdir -p internal/web/dist
@find internal/web/dist -mindepth 1 -maxdepth 1 ! -name stub.html -exec rm -rf {} +
@cp -R web/dist/. internal/web/dist/
node scripts/check-web-assets.mjs
# Validate Vite's parsed release graph against the staged embed (runs as the
# final step of web-embed). The node test drives the same validator through
# missing, escaping, external, hidden/credential, and stale cases.
web-assets-check: web-embed
node --test scripts/check-web-assets.test.mjs
smoke-web-release:
node --test scripts/smoke-web-release.test.mjs
bash scripts/smoke-web-release.sh
# Format code
fmt:
go fmt ./...
# Install the pinned linter used by CI; lint-ci runs this first, so skip it when already installed.
lint-tools:
@if [ "$$("$(GOLANGCI_LINT_BIN)" version --short 2>/dev/null)" = "$(GOLANGCI_LINT_VERSION:v%=%)" ]; then exit 0; fi; \
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)
# Build golangci-lint with the plugins in .custom-gcl.yml into the
# repository-owned tool path. Strip repo-local Git variables so a build run
# from the commit hook does not inherit GIT_DIR.
custom-gcl: lint-tools
@mkdir -p "$(CI_TOOLS_BIN)"
@unset_args=$$(git rev-parse --local-env-vars 2>/dev/null | sed 's/^/-u /' | tr '\n' ' '); \
env $$unset_args GOFLAGS=-buildvcs=false "$(GOLANGCI_LINT_BIN)" custom \
--destination "$(CI_TOOLS_BIN)" --name custom-gcl \
--version "$(GOLANGCI_LINT_VERSION)"
# Run linter (auto-fix)
lint: custom-gcl
@mkdir -p "$(GOLANGCI_LINT_TMP)"
TMPDIR="$(GOLANGCI_LINT_TMP)" "$(CUSTOM_GCL_BIN)" run --fix ./...
# Check the shared Huma API contract.
huma-check:
go run go.kenn.io/kit/cmd/huma-check@$(HUMA_CHECK_VERSION) ./...
.PHONY: huma-check
# Run linter (CI, no auto-fix)
lint-ci: custom-gcl testify-helper-check
@mkdir -p "$(GOLANGCI_LINT_TMP)"
TMPDIR="$(GOLANGCI_LINT_TMP)" "$(CUSTOM_GCL_BIN)" run ./...
@if [ -n "$$GITHUB_PATH" ]; then \
$(MAKE) --no-print-directory vuln-tools; \
printf '%s\n' "$(CI_TOOLS_BIN)" >> "$$GITHUB_PATH"; \
fi
# Install and run the scanner from a repository-owned path so a stale tool
# installed by the base branch's pull-request workflow cannot replace it.
vuln-tools:
@mkdir -p "$(CI_TOOLS_BIN)"
GOBIN="$(CI_TOOLS_BIN)" go install golang.org/x/vuln/cmd/govulncheck@$(GOVULNCHECK_VERSION)
vulncheck: vuln-tools
"$(GOVULNCHECK_BIN)" -tags "$(BUILD_TAGS)" ./...
# Enforce testify helper usage and named sub-second polling budgets in tests
testify-helper-check:
go run ./cmd/testify-helper-check -tags="$(BUILD_TAGS)" ./...
# Install pre-commit hook via prek
install-hooks:
@if ! command -v prek >/dev/null 2>&1; then \
echo "prek not found. Install with: brew install prek" >&2; \
exit 1; \
fi
@HOOKS_PATH=$$(git config --get core.hooksPath 2>/dev/null); \
if [ "$$HOOKS_PATH" = ".githooks" ]; then \
git config --unset core.hooksPath; \
elif [ -n "$$HOOKS_PATH" ]; then \
echo "core.hooksPath is set to '$$HOOKS_PATH' — unset it first if intended" >&2; \
exit 1; \
fi
prek install
# Tidy dependencies
tidy:
go mod tidy
# Run benchmarks (query engine smoke test)
bench:
go test -tags "$(BUILD_TAGS)" -run=^$$ -bench=. -benchtime=1s -count=1 ./internal/query/
# Install docs dependencies
docs-install:
cd docs && uv sync --frozen
# Build docs site
docs-build:
cd docs && bash ./vercel-build.sh
# Serve the complete site locally with the same layout used by deployment.
docs-serve: docs-build
cd docs && uv run --frozen python -m http.server 8000 --bind 127.0.0.1 --directory site
# Check docs sources and build output
docs-check:
bash scripts/check-docs.sh
# Run the deterministic fixture selector and review-report unit tests.
docs-fixture-test:
python3 -m unittest docs/fixtures/test_select_enron_fixture.py
# Validate the pinned, manually reviewed docs-fixtures branch in a disposable
# directory. The explicit offline mode is a local opt-in and prints SKIP.
docs-fixture-check:
@fixture_tmp="$$(mktemp -d /tmp/msgvault-docs-fixture-check.XXXXXX)"; \
trap 'rm -rf "$$fixture_tmp"' EXIT; \
bash docs/fixtures/hydrate-fixture.sh --output-dir "$$fixture_tmp"
# Exercise the real importer, cache, daemon, and relationship API against the
# hydrated fixture. This is deliberately outside make test.
docs-fixture-smoke:
@fixture_tmp="$$(mktemp -d /tmp/msgvault-docs-fixture-smoke.XXXXXX)"; \
trap 'rm -rf "$$fixture_tmp"' EXIT; \
bash docs/fixtures/hydrate-fixture.sh --output-dir "$$fixture_tmp/fixture"; \
bash docs/fixtures/run-smoke.sh "$$fixture_tmp/fixture"
# Generate docs screenshots from the isolated real-daemon fixture pipeline.
docs-web-screenshots:
bash docs/screenshots/generate-web-fixture-screenshots.sh
# Regenerate docs screenshots
docs-screenshots:
bash docs/screenshots/generate-all.sh
# Publish curated static docs assets to local asset branch
docs-assets-branch:
bash docs/assets/update-static-assets-branch.sh
# Publish generated docs assets to local asset branch
docs-generated-assets-branch:
bash docs/screenshots/update-generated-assets-branch.sh
# Build locally so the sibling website/ directory is available to both deploys.
# Deploy docs to Vercel staging
docs-deploy-staging:
cd docs && vercel pull --yes --environment=preview
cd docs && vercel build
cd docs && vercel deploy --prebuilt
# Deploy docs to Vercel production
docs-deploy:
cd docs && vercel pull --yes --environment=production
cd docs && vercel build --prod
cd docs && vercel deploy --prebuilt --prod
# Build the MIME shootout tool
shootout:
CGO_ENABLED=1 go build -o mimeshootout ./scripts/mimeshootout
# Run MIME shootout
run-shootout: shootout
./mimeshootout -limit 1000
# Show help
help:
@echo "msgvault build targets:"
@echo ""
@echo " build - Debug build"
@echo " build-release - Release build (optimized, stripped)"
@echo " install - Install to ~/.local/bin or GOPATH"
@echo ""
@echo " test - Run SQLite tests with automatic CPU/memory scaling (TEST_PROFILE=standard disables)"
@echo " test-v - Run tests (verbose)"
@echo " test-shards - Run SHARDED_TEST_PKGS as TEST_SHARDS concurrent processes each"
@echo " test-unsharded - Run every package except SHARDED_TEST_PKGS (CI's test lane)"
@echo " fmt - Format code"
@echo " lint - Run linter (auto-fix)"
@echo " lint-ci - Run linter (CI, no auto-fix; also runs testify-helper-check)"
@echo " vulncheck - Run the pinned Go vulnerability scanner"
@echo " testify-helper-check - Enforce testify helpers and polling budgets in tests"
@echo " tidy - Tidy go.mod"
@echo " vcard-registry-check - Network-check IANA registry drift (manual; not CI)"
@echo " vcard-registry-update - Update the vendored IANA vCard registry"
@echo " openapi - Regenerate OpenAPI specs and generated Go client"
@echo " openapi-check - Check committed OpenAPI specs and generated Go client are up to date"
@echo " api-check - Alias for openapi-check"
@echo " web-install - Install pinned browser application dependencies"
@echo " web-generate - Regenerate browser API types from the OpenAPI schema"
@echo " web-check - Check browser types and generated API artifacts"
@echo " web-test - Run browser application unit tests"
@echo " web-test-browser - Run browser application Playwright tests"
@echo " web-build - Build the browser application"
@echo " web-embed - Build, stage, and validate browser assets for Go embedding"
@echo " web-assets-check - Validate the release asset graph and run the validator's tests"
@echo " smoke-web-release - Build and exercise an isolated release-style daemon"
@echo " install-hooks - Install pre-commit hook via prek"
@echo " clean - Remove build artifacts"
@echo ""
@echo " docs-install - Install docs dependencies"
@echo " docs-build - Build docs site"
@echo " docs-serve - Build and serve the complete site locally"
@echo " docs-check - Run docs validation"
@echo " docs-screenshots - Regenerate docs screenshots"
@echo " docs-assets-branch - Publish static docs assets branch"
@echo " docs-generated-assets-branch - Publish generated docs assets branch"
@echo " docs-deploy-staging - Deploy docs to Vercel staging"
@echo " docs-deploy - Deploy docs to Vercel production"
@echo ""
@echo " bench - Run query engine benchmarks"
@echo " shootout - Build MIME shootout tool"
@echo " run-shootout - Run MIME shootout"