Files
silo-server/Makefile
T
QuickandClaude Opus 5 24ee99523d feat(settings): generate typed bindings for all four languages
One generator rather than one per repo. The point of the contract is that
four codebases agree on keys, types, scopes and defaults, and four
independently written generators would be four chances to disagree.

Go and TypeScript land in this repo; Kotlin and Swift are written into the
sibling client checkouts, skipped with a note when they are not present so
a server-only developer can still run it. Output is sorted by key so an
unrelated manifest edit does not produce spurious diffs.

The Kotlin output is the interesting one: it generates the DeviceSettings
allowlist Android maintained by hand, plus the BOOLEAN_KEYS/INT_KEYS/
DOUBLE_KEYS classification it kept as a *second* hand-maintained table that
had to agree with the first. Both are manifest questions now, so the whole
class of "wrote a local key to the server" and "flushed a value the store
could not parse" bugs stops being possible by construction.

The TypeScript output carries the full definition table — labels, controls,
enum members, bounds — so web/src/lib/settingsManifest.ts can be deleted
rather than kept in sync: it declared 17 definitions against the contract's
49, with its own two-scope model that does not match the contract's five.

make verify-settings-bindings fails when the committed output disagrees
with the manifest, wired into CI, so a manifest change cannot merge leaving
every client reading stale keys.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 00:05:57 +00:00

171 lines
7.0 KiB
Makefile

.PHONY: frontend build dev-frontend dev-backend dev-proxy dev-transcode lint test test-go test-web embed-stub clean jellyfin-web migrate-continuum-check verify-local-paths install-hooks migrate-create migrate-validate migrate-status migrate-up settings-bindings verify-settings-bindings
GIT_COMMON_DIR := $(strip $(shell git rev-parse --git-common-dir 2>/dev/null))
MAIN_CHECKOUT_ROOT := $(if $(GIT_COMMON_DIR),$(abspath $(GIT_COMMON_DIR)/..))
SHARED_MAKEFILE_LOCAL := $(if $(GIT_COMMON_DIR),$(abspath $(GIT_COMMON_DIR)/../Makefile.local))
DEFAULT_PLUGIN_SDK_DIR := $(abspath ../silo-plugin-sdk)
SHARED_PLUGIN_SDK_DIR := $(if $(MAIN_CHECKOUT_ROOT),$(abspath $(MAIN_CHECKOUT_ROOT)/../silo-plugin-sdk))
GOOSE := go run github.com/pressly/goose/v3/cmd/goose@v3.27.1
GOOSE_DIR := migrations/sql
ENV_FILE ?= .env
ifneq ($(wildcard $(DEFAULT_PLUGIN_SDK_DIR)),)
DEV_PLUGIN_SDK_DIR ?= $(DEFAULT_PLUGIN_SDK_DIR)
else ifneq ($(wildcard $(SHARED_PLUGIN_SDK_DIR)),)
DEV_PLUGIN_SDK_DIR ?= $(SHARED_PLUGIN_SDK_DIR)
endif
JELLYFIN_WEB_INSTALL_DIR ?= .local/compat/jellyfin-web
JELLYFIN_WEB_VERSION ?= 10.11.6
# Build version stamping: inject the git revision so the admin Build panel shows a
# version even when Go's VCS metadata isn't embedded (mirrors the Dockerfile ldflags).
BUILDINFO_PKG := github.com/Silo-Server/silo-server/internal/buildinfo
BUILD_REVISION ?= $(shell git rev-parse HEAD 2>/dev/null)
BUILD_DIRTY ?= $(shell test -n "$$(git status --porcelain 2>/dev/null)" && echo true || echo false)
GO_LDFLAGS := -X $(BUILDINFO_PKG).revisionOverride=$(BUILD_REVISION) -X $(BUILDINFO_PKG).dirtyOverride=$(BUILD_DIRTY)
# Build the frontend (requires pnpm)
frontend:
cd web && pnpm install --frozen-lockfile && pnpm run build
# Build the Go binary (depends on frontend)
build: frontend
go build -ldflags "$(GO_LDFLAGS)" -o silo ./cmd/silo/
# Run frontend dev server (proxies API to localhost:8080)
dev-frontend:
cd web && pnpm run dev
# Run the Go backend (integrated mode)
dev-backend:
go run ./cmd/silo/
# Run a proxy node (stateless stream proxy, no DB required)
dev-proxy:
go run ./cmd/silo/ --mode=proxy
# Run a transcode node (HLS transcode worker, no DB required)
dev-transcode:
go run ./cmd/silo/ --mode=transcode
# Lint Go and frontend code
lint:
golangci-lint run
cd web && pnpm run lint
# Frontend test files that fail on main today. This list is shrink-only: delete
# an entry along with its fix, and never extend it to land a change. The Go
# suite has no equivalent — a Go test that cannot pass yet carries a t.Skip and
# its reason in the source, where whoever reads the test finds it.
WEBTEST_KNOWN_FAILURES := \
--exclude src/pages/Catalog.test.tsx \
--exclude src/pages/ItemDetail/SeasonContent.test.tsx \
--exclude src/pages/LibraryRecommended.test.tsx \
--exclude src/pages/audiobooks/player/useAudiobookPlayback.test.ts \
--exclude src/pages/setup-wizard/steps/ServerStorageStep.test.tsx \
--exclude src/player/hooks/useASSSubtitles.test.tsx
# The Go binary embeds the built frontend, so every Go build and test needs
# web/dist to exist. Tests never serve it, so a placeholder is enough; `make
# build` still builds the real bundle.
embed-stub:
@mkdir -p web/dist
@[ -e web/dist/index.html ] || printf '<!doctype html>\n' > web/dist/index.html
# Run the Go and frontend test suites.
test: test-go test-web
test-go: embed-stub
go test ./...
test-web:
cd web && pnpm exec vitest run $(WEBTEST_KNOWN_FAILURES)
# Regenerate the settings-contract bindings for every language.
#
# The client repos are siblings of this one (see CLAUDE.md); a missing checkout
# is skipped rather than failing, so a server-only developer can still run this.
SILO_ANDROID_DIR ?= $(abspath ../silo-android)
SILO_APPLE_DIR ?= $(abspath ../silo-apple)
settings-bindings:
@mkdir -p internal/settingskeys
go run ./cmd/settingsgen -lang go -out internal/settingskeys/keys.go
gofmt -w internal/settingskeys/keys.go
go run ./cmd/settingsgen -lang ts -out web/src/lib/settingsContract.ts
@cd web && pnpm exec prettier --write src/lib/settingsContract.ts >/dev/null
@if [ -d "$(SILO_ANDROID_DIR)" ]; then \
go run ./cmd/settingsgen -lang kotlin \
-out "$(SILO_ANDROID_DIR)/shared/src/commonMain/kotlin/org/siloserver/silo/model/settings/SettingKeys.kt"; \
echo "wrote Kotlin bindings to $(SILO_ANDROID_DIR)"; \
else \
echo "skipping Kotlin: $(SILO_ANDROID_DIR) not checked out"; \
fi
@if [ -d "$(SILO_APPLE_DIR)" ]; then \
go run ./cmd/settingsgen -lang swift \
-out "$(SILO_APPLE_DIR)/iosApp/iosApp/Networking/SettingKeys.generated.swift"; \
echo "wrote Swift bindings to $(SILO_APPLE_DIR)"; \
else \
echo "skipping Swift: $(SILO_APPLE_DIR) not checked out"; \
fi
# Fail when the committed bindings disagree with the manifest, so a manifest
# change cannot merge without regenerating what every client reads.
verify-settings-bindings:
@go run ./cmd/settingsgen -lang go > /tmp/silo-settings-go.check
@gofmt /tmp/silo-settings-go.check > /tmp/silo-settings-go.fmt
@diff -u internal/settingskeys/keys.go /tmp/silo-settings-go.fmt \
|| { echo "::error::internal/settingskeys/keys.go is stale; run make settings-bindings"; exit 1; }
@echo "settings bindings are current"
# Check committed content for local machine path leaks.
verify-local-paths:
scripts/check-local-path-leaks.sh
# Create a timestamped Goose SQL migration. Usage: make migrate-create NAME=add_thing
migrate-create:
@if [ -z "$(NAME)" ]; then echo "usage: make migrate-create NAME=add_thing"; exit 1; fi
$(GOOSE) -dir $(GOOSE_DIR) create "$(NAME)" sql
# Validate Goose migration annotations and SQL parsing without touching a database.
migrate-validate:
$(GOOSE) -dir $(GOOSE_DIR) validate
# Show Goose migration status through Silo's bootstrapping runner.
migrate-status:
go run ./cmd/silo/ --env "$(ENV_FILE)" --migrate-status
# Apply pending Goose migrations through Silo's bootstrapping runner.
migrate-up:
go run ./cmd/silo/ --env "$(ENV_FILE)" --migrate-only
# Install repo-local git hooks for this checkout/worktree.
install-hooks:
@existing="$$(git config --local core.hooksPath 2>/dev/null || true)"; \
if [ -n "$$existing" ] && [ "$$existing" != ".githooks" ]; then \
echo "warning: overwriting existing local core.hooksPath ($$existing) with .githooks"; \
fi
git config core.hooksPath .githooks
# Fetch and build the pinned Jellyfin Web component into a gitignored local cache.
jellyfin-web:
go run ./cmd/silo/ compat-web install --dir "$(JELLYFIN_WEB_INSTALL_DIR)" --version "$(JELLYFIN_WEB_VERSION)"
# Read-only preflight for Continuum Docker installs moving to Silo.
migrate-continuum-check:
scripts/migrate-continuum-docker.sh check
# Clean build artifacts
clean:
rm -rf web/dist web/node_modules silo
# Include developer-specific targets (gitignored, optional).
# In Git worktrees, fall back to the main checkout's Makefile.local so custom
# targets like dev-deploy work without per-worktree symlinks or copies.
ifneq ($(wildcard Makefile.local),)
include Makefile.local
else ifneq ($(wildcard $(SHARED_MAKEFILE_LOCAL)),)
include $(SHARED_MAKEFILE_LOCAL)
endif