.PHONY: docs # Load configuration from .env (production settings, ports, etc.) -include .env # Function to strip single/double quotes using Make functions define strip_quotes $(subst ",,$(subst ',,$($(1)))) endef # Disable printing directory traversal on task start/end MAKEFLAGS += --no-print-directory # Constants IMAGE_NAME=sdvd/server TEST_CLIENT_IMAGE_NAME=sdvd/test-client IMAGE_VERSION ?= local DOCKERFILE_PATH=docker/Dockerfile TEST_CLIENT_DOCKERFILE_PATH=docker/Dockerfile.test-client # Build configuration (Debug for local, Release for CI/production) BUILD_CONFIGURATION ?= Debug # Docker build progress output (plain, tty, auto, quiet) DOCKER_PROGRESS ?= plain # Dev overlay adds the sidecar build contexts; explicit -f drops the automatic override merge, so re-add it. COMPOSE := docker compose -f docker-compose.yml -f docker-compose.dev.yml $(if $(wildcard docker-compose.override.yml),-f docker-compose.override.yml) # Export IMAGE_VERSION for usage in docker compose commands export IMAGE_VERSION # Steam build credentials for Docker image builds (game download). # By default these come from .env (loaded above). When the test harness # calls `make build`, it overrides these via command-line variables # (e.g. `make build STEAM_USERNAME=x`), which take precedence over # -include .env values in GNU Make. export STEAM_USERNAME := $(call strip_quotes,STEAM_USERNAME) export STEAM_PASSWORD := $(call strip_quotes,STEAM_PASSWORD) export STEAM_REFRESH_TOKEN := $(call strip_quotes,STEAM_REFRESH_TOKEN) # Cross-platform ISO 8601 UTC timestamp (safe for use with filenames etc.) ifeq ($(OS),Windows_NT) TIMESTAMP := $(shell powershell -NoProfile -Command "(Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH-mm-ss')")Z else TIMESTAMP := $(shell date -u '+%Y-%m-%dT%H-%M-%S')Z endif # Short git commit ("-dirty" when the tree is modified), baked in as SDVD_GIT_SHA so the diagnostics # tool can identify a local build. `--match=` matches no tag, leaving --always to yield the bare # commit; unquoted because cmd.exe would pass the quotes through to git. Empty outside a git checkout, # where build-server omits the --build-arg so the Dockerfile default applies (passing it empty would # set the ARG to ""). CI builds via docker/build-push-action and passes its own github.sha. GIT_SHA := $(shell git describe --always --dirty --match=) # Install development dependencies install: @echo Installing development dependencies... @npm ci @dotnet tool restore @echo Setup complete. Git hooks are now active. # Build all production docker images (server + steam-service) build: build-server build-steam-service # Build server docker image (downloads game during build for mod compilation) build-server: @echo Building image `$(IMAGE_NAME):$(IMAGE_VERSION)` with BUILD_CONFIGURATION=$(BUILD_CONFIGURATION)... @docker buildx build \ --platform=linux/amd64 \ --build-arg BUILD_CONFIGURATION=$(BUILD_CONFIGURATION) \ $(if $(GIT_SHA),--build-arg SDVD_GIT_SHA=$(GIT_SHA)) \ -t $(IMAGE_NAME):$(IMAGE_VERSION) \ $(if $(filter-out local,$(IMAGE_VERSION)),-t $(IMAGE_NAME):latest) \ --secret id=steam_username,env=STEAM_USERNAME \ --secret id=steam_password,env=STEAM_PASSWORD \ --secret id=steam_refresh_token,env=STEAM_REFRESH_TOKEN \ -f $(DOCKERFILE_PATH) \ --load \ --progress=$(DOCKER_PROGRESS) \ . @echo Server build complete. # Build steam-service docker image build-steam-service: @echo Building steam-service image... @$(COMPOSE) build steam-auth @echo Steam-service build complete. # Build test client docker image (for containerized E2E tests) build-test-client: @echo Building test client image `$(TEST_CLIENT_IMAGE_NAME):$(IMAGE_VERSION)`... @docker buildx build \ --platform=linux/amd64 \ -t $(TEST_CLIENT_IMAGE_NAME):$(IMAGE_VERSION) \ --secret id=steam_username,env=STEAM_USERNAME \ --secret id=steam_password,env=STEAM_PASSWORD \ --secret id=steam_refresh_token,env=STEAM_REFRESH_TOKEN \ -f $(TEST_CLIENT_DOCKERFILE_PATH) \ --load \ --progress=$(DOCKER_PROGRESS) \ . @echo Test client build complete. # Build and run everything up: build @echo Starting server `$(IMAGE_NAME):$(IMAGE_VERSION)`... @$(COMPOSE) up -d --build @echo Server is now running. Use `make cli` or `make logs` to view output. # Authenticate Steam accounts and download game files (interactive). # Passes .env.test (if present) so steam-service sees both production # credentials (STEAM_USERNAME/PASSWORD from .env via compose) and test # accounts (STEAM_ACCOUNTS JSON from .env.test). Accounts with saved # sessions are skipped; only new accounts prompt for Steam Guard. setup: build-steam-service @$(COMPOSE) run --rm -it \ $(if $(wildcard .env.test),--env-from-file .env.test) \ steam-auth setup @echo Setup complete. Saved sessions are stored in the steam-session volume. restart: @echo Restarting server `$(IMAGE_NAME):$(IMAGE_VERSION)`... @$(COMPOSE) restart @echo Server restarted. Use `make cli` or `make logs` to view output. # Stop the server down: @echo Stopping server... @$(COMPOSE) down --remove-orphans # Attach to interactive split-pane server CLI cli: @$(COMPOSE) exec server attach-cli # Collect a server-state diagnostics bundle (wizard + zip on the host under ./diagnostics) diagnostics: @$(COMPOSE) exec -it server diagnostics # View server logs (escape sequence to reset colors) logs: @$(COMPOSE) logs -f -@bun -e "process.stdout.write('\x1b[0m')" dumplogs: @echo "Writing logs to logs_$(TIMESTAMP).txt" @$(COMPOSE) logs > "logs_$(TIMESTAMP).txt" # Start docs dev server (extracts OpenAPI spec from Docker image first) docs: @echo Extracting OpenAPI spec from $(IMAGE_NAME):$(IMAGE_VERSION) image... @bun -e "require('fs').mkdirSync('docs/assets', { recursive: true })" -@bun -e "try{require('child_process').execSync('docker rm -f openapi-extract',{stdio:'ignore'})}catch(e){}" @docker create --name openapi-extract $(IMAGE_NAME):$(IMAGE_VERSION) @docker cp openapi-extract:/data/openapi.json docs/assets/openapi.json @docker rm openapi-extract @echo OpenAPI spec ready. @bun --cwd=./docs run dev # Regenerate the documentation page-relationship graph (docs/.output/page-graph.html) docs-graph: @bun --cwd=./docs run graph @echo "Open docs/.output/page-graph.html in a browser." # Clean up everything, including all volumes clean: @echo Cleaning up... @IMAGE_VERSION=$(IMAGE_VERSION) $(COMPOSE) down -v -@docker rmi $(IMAGE_NAME):$(IMAGE_VERSION) $(IMAGE_NAME):latest # Test project paths TEST_PROJECT := ./tests/JunimoServer.Tests RUNNER_PROJECT := ./tests/JunimoServer.TestRunner # Test filtering. Use FILTER to run specific tests (case-insensitive substring of the # class FullName or {Class}.{Method} display name). Separate alternatives with '|': # make test FILTER=PasswordProtection # make test FILTER="Login_WithCorrectPassword" # make test FILTER="CabinStrategyNoneTests|CabinPositionPersistenceTests" # make test (runs all tests) # Note: '|' works only for direct `make test`. The !run-tests-e2e PR-comment trigger # rejects '|' (it would break the sticky-comment markdown table); use one pattern there. FILTER ?= # Verbose output. Use VERBOSE=1 for detailed setup steps and diagnostic logs: # make test VERBOSE=1 VERBOSE ?= export COLUMNS=160 # Run tests with custom runner (CI mode - streaming output) test: @dotnet run --project $(RUNNER_PROJECT) -- $(if $(VERBOSE),--verbose) $(if $(FILTER),--filter "$(FILTER)") # Run unit tests (no Docker, no Steam) test-unit: @dotnet test tests/SteamService.Tests/SteamService.Tests.csproj @dotnet test tests/JunimoServer.UnitTests/JunimoServer.UnitTests.csproj # Run tests with verbose output (detailed setup steps, diagnostics inline) test-verbose: @dotnet run --project $(RUNNER_PROJECT) -- --verbose $(if $(FILTER),--filter "$(FILTER)") # Run tests with structured JSONL output (for LLM/AI agents) # Sets SDVD_TEST_TRACING=full for the AI-debug context (respBody capture, # X-Request-Id on reads, wait_started). An SDVD_TEST_TRACING line in .env.test # overrides it: TestEnvLoader clobbers process env. test-llm: export SDVD_TEST_TRACING=full test-llm: @dotnet run --project $(RUNNER_PROJECT) -- --llm $(if $(FILTER),--filter "$(FILTER)") # Build the test UI frontend (required for --web mode) build-test-ui: ifeq ($(OS),Windows_NT) @where bun >NUL 2>&1 || (echo "Error: bun is required for test-ui. Install: https://bun.sh" && exit 1) else @command -v bun >/dev/null || (echo "Error: bun is required for test-ui. Install: https://bun.sh" && exit 1) endif @cd tests/test-ui && bun install && bun run build # Run tests with web UI (opens browser with live results) test-web: build-test-ui @dotnet run --project $(RUNNER_PROJECT) -- --web $(if $(VERBOSE),--verbose) $(if $(FILTER),--filter "$(FILTER)") # Run tests with web UI and generate static report test-web-report: build-test-ui @dotnet run --project $(RUNNER_PROJECT) -- --web --report $(if $(VERBOSE),--verbose) $(if $(FILTER),--filter "$(FILTER)") # Render OG (social link-preview) card mockups to .output/og-preview/ for design review og-preview: @dotnet run --project $(RUNNER_PROJECT) -- preview-og # --- Test Observability Targets --- # All run via tools/test-observability.ts so they work from any shell (PowerShell, # cmd, bash) with no jq/python/awk/column dependency. Latest-run resolution and # all queries live in the script; these targets just pass args through. # Show test run summary (failures, timing, classification) test-summary: @bun tools/test-observability.ts summary # Show per-test event log (Usage: make test-events TEST=ClassName.MethodName[(arg=...)]) # Filters infrastructure.jsonl by test displayName; matches both fact and theory tests. test-events: @bun tools/test-observability.ts events TEST="$(TEST)" # Show infrastructure lifecycle log (server/client creation, capacity, sessions) test-infra-log: @bun tools/test-observability.ts infra-log # Show full lifecycle log for a container (Usage: make test-container-log CONTAINER=server-0) test-container-log: @bun tools/test-observability.ts container-log CONTAINER="$(CONTAINER)" # Show run metadata (git, env, runtime info) test-metadata: @bun tools/test-observability.ts metadata # Show flakiness data across recent runs test-flaky: @bun tools/test-observability.ts flaky # Rank tests by runtime across recent runs to find suite-shortening targets. # Reads the per-test timing already in flakiness.jsonl (same file as test-flaky). # Default sort is total observed slot-time (sum of sampled durations) — the best # proxy for "improving this shortens the whole suite", since tests run in parallel # and a frequently-run or theory-multiplied test consumes more wall-clock than a # slow-but-rare singleton. # Usage: make test-slowest [N=15] [SORT=total|p50|p90|max] [FIELD=durationMs|testBodyMs] [LASTRUNS=20] [MINRUNS=3] N ?= 15 SORT ?= total FIELD ?= durationMs LASTRUNS ?= 20 MINRUNS ?= 3 test-slowest: @bun tools/test-observability.ts slowest N=$(N) SORT=$(SORT) FIELD=$(FIELD) LASTRUNS=$(LASTRUNS) MINRUNS=$(MINRUNS) # Dump the last few failure_context events from the latest run for quick triage. test-diagnose: @bun tools/test-observability.ts diagnose # Verify analyzer style rules and formatting without writing (builds the solution). # Biome covers the JS/TS projects scoped in biome.jsonc. lint-check: @dotnet format style JunimoServer.slnx --severity error --verify-no-changes @dotnet csharpier check . @npx @biomejs/biome check . # Auto-fix analyzer style violations (braces, namespaces, usings), then re-format. lint-fix: @dotnet format style JunimoServer.slnx --severity error @dotnet csharpier format . @npx @biomejs/biome check --write --unsafe . # Show help help: @echo Stardew Valley Dedicated Server @echo "" @echo Targets: @echo " make install - Install development dependencies (commitlint, git hooks)" @echo " make setup - Run first-time Steam authentication and game download" @echo " make up - Build and start server" @echo " make build - Build all production images (server + steam-service)" @echo " make logs - View server logs" @echo " make dumplogs - Dump server logs to file on host" @echo " make cli - Attach to interactive server console (tmux-based)" @echo " make diagnostics - Collect a server-state diagnostics zip (host ./diagnostics/)" @echo " make down - Stop the server" @echo " make restart - Restart the server (preserves volumes)" @echo " make docs - Start docs dev server (requires built image)" @echo " make docs-graph - Regenerate the docs page-relationship graph (HTML)" @echo " make clean - Remove ALL containers, volumes and images" @echo "" @echo Testing: @echo " make test - Run E2E tests (CI mode, streaming output)" @echo " make test-verbose - Run tests with detailed setup/diagnostic output" @echo " make test-llm - Run tests with structured JSONL output (for AI agents)" @echo " make test-web - Run tests with web UI (opens browser with live results)" @echo " make test-web-report - Run tests with web UI + static report generation" @echo " make test-unit - Run unit tests (no Docker, no Steam)" @echo " make og-preview - Render OG link-preview card mockups for design review" @echo " FILTER=X - Filter tests, e.g. FILTER=PasswordProtection" @echo " VERBOSE=1 - Show detailed setup steps, e.g. make test VERBOSE=1" @echo "" @echo " make build-test-client - Build test client container image (for E2E tests)" @echo "" @echo Formatting: @echo " make lint-check - Verify C# + JS/TS style rules + formatting (slow, builds)" @echo " make lint-fix - Auto-fix C# + JS/TS style violations + re-format (slow, builds)" @echo "" @echo Test Observability: @echo " make test-summary - Show test run summary (failures, timing)" @echo " make test-events - Show per-test events (TEST=Class.Method)" @echo " make test-infra-log - Show infrastructure lifecycle log" @echo " make test-metadata - Show run metadata (git, env, runtime)" @echo " make test-flaky - Show flakiness data across recent runs" @echo " make test-slowest - Rank tests by runtime (SORT=total|p50|p90|max) to find suite-shortening targets" @echo " make test-diagnose - Show last failure_context events for triage" @echo " make test-container-log - Show full container log (Usage: CONTAINER=server-0)" @echo "" @echo Note: Use GitHub Actions for building and pushing release images .DEFAULT_GOAL := help