The contract manifest landed as a document nothing checked. This makes it a mechanism, and fixes the one defect in the change set that hurt users on merge rather than at cutover. Web appearance cache. useTheme cleared the cache for any account whose stamp did not match and never repopulated it — the only writers were the four user-action setters — so every upgrading user lost their warm start on every load, not once, and x-large-text and high-contrast users lost theirs too. The owner-stamp protocol is replaced with per-account key namespacing (`silo-theme:7`): a foreign value is absent rather than present-and-distrusted, so nothing has to be deleted, the first account keeps its warm start, and there is no shared stamp for a second tab, a stale debounce timer, or an out-of-order effect to race on. Widening ownership to profile scope, which this manifest requires, is now a change to appearanceCacheOwner alone. Adds the API-to-cache mirror useTheme was missing, cancels pending debounced writes across an account change, and re-seeds provider state during render so no frame paints the previous account's look. Canonicalization. writeCanonical used json.Marshal, which HTML-escapes < > and &, and canonicalNumber used Go's 'g' format — both diverge from RFC 8785, so the first label containing an ampersand or bound below 1e-4 would have forked the server's ETag from every conforming client. Output is now byte-identical to ECMAScript String() across the edge cases, verified against node. The ETag also covers the value schemas, which decide what the server accepts and previously could change while the tag stood still. All four derived representations are memoized; a conditional GET no longer costs a full parse and re-serialize. Validation. strictUnmarshal's decoder.More() answered false for a stray ] or }, so `true]` validated as a boolean. Enum matching compared fmt.Sprintf tokens, so the string "3" satisfied an integer member. Declared steps were never enforced. The language pattern rejected tags both mobile platforms emit unprompted (en_US, ca-ES-valencia, ar-EG-u-nu-latn) and never normalized case, so en-US and en-us were two rows for one preference; NormalizeValue now canonicalizes on the shared path. Manifest. show_forced_subtitles defaulted false where the server column is NOT NULL DEFAULT true, which would have turned forced subtitles off for every profile that never touched it. preferred_quality declared 13 members where the planner speaks 6 and collapses the rest to auto. metadata_language's allowlist was bound to the very column it migrates from. subtitle-appearance pinned fontFamily to three families while Apple stores any installed system font. Registers five user-facing settings the clients already ship, and corrects three notes that described Android behaviour that was not true. Enforcement. The package had no non-test callers, so MustLoad never ran; it now loads and logs at startup. The inventory test compared the manifest against a hand-copied map and could not see the drift it named; it now iterates settingsRegistry and checks defaults too — both verified to fail on injected drift. Adds .github/workflows/ci.yml, the repo's first CI that runs go test, go vet, gofmt, and the frontend suite. Known pre-existing failures are named individually in the Makefile so everything else stays gated and the list can only shrink. Part of #135 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
138 lines
5.6 KiB
Makefile
138 lines
5.6 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
|
|
|
|
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
|
|
|
|
# Tests that fail on main today and are tracked separately. Nothing in this list
|
|
# is owned by the change that added it — the point of naming them individually,
|
|
# rather than skipping whole packages, is that everything else stays gated and
|
|
# the list can only shrink. Delete an entry along with its fix; do not add.
|
|
GOTEST_KNOWN_FAILURES := TestHandleReplanPlaybackV3SeekFailureRecoveryNeverChangesMediaVersion|TestAutoscanMediaUpdatedIgnoresUnsupportedSidecars|TestAutoscanMediaUpdatedSidecarsScanParent|TestAutoscanMediaUpdatedRootSidecarDoesNotScanLibrary
|
|
|
|
# Frontend test files that fail on main today. Same rules as
|
|
# GOTEST_KNOWN_FAILURES: shrink-only, and never extend it to land a change.
|
|
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 -skip '$(GOTEST_KNOWN_FAILURES)' ./...
|
|
|
|
test-web:
|
|
cd web && pnpm exec vitest run $(WEBTEST_KNOWN_FAILURES)
|
|
|
|
# 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
|