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>
171 lines
7.0 KiB
Makefile
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
|