Files
kandev/Makefile
T

682 lines
25 KiB
Makefile

# Kandev Root Makefile
# Orchestrates both backend (Go) and web app (Vite/React)
# Directories
BACKEND_DIR := apps/backend
WEB_DIR := apps/web
APPS_DIR := apps
DESKTOP_DIR := apps/desktop
DESKTOP_RUNTIME_DIR := $(DESKTOP_DIR)/src-tauri/resources/kandev
EMBEDDED_WEB_DIR := $(BACKEND_DIR)/internal/webapp/embedded/generated
# Tools
PNPM := pnpm
GOFLAGS ?= -v
MAKE := make
# Cross-platform commands
ifeq ($(OS),Windows_NT)
RM = cmd /c del /s /q
RMDIR = cmd /c rmdir /s /q
else
RM = rm -f
RMDIR = rm -rf
endif
# stderr redirect for $(shell ...) probes. Keyed on $(OS)$(MSYSTEM) rather than
# $(OS) alone: Git Bash sets OS=Windows_NT yet runs commands through sh, so it
# needs the POSIX path. Under native cmd there is no /dev/null, and cmd resolves
# redirections before running the command — `2>/dev/null` would abort the probe
# and silently force its fallback. Holds the whole token so callers can blank it.
# Mirrors apps/backend/Makefile.
ifeq ($(OS)$(MSYSTEM),Windows_NT)
NULL_REDIR := 2>NUL
else
NULL_REDIR := 2>/dev/null
endif
# Colors for terminal output
RESET := \033[0m
BOLD := \033[1m
DIM := \033[2m
GREEN := \033[32m
BLUE := \033[34m
CYAN := \033[36m
YELLOW := \033[33m
MAGENTA := \033[35m
VERBOSE ?= 0
NODE ?= $(shell command -v node $(NULL_REDIR) || echo node)
RUNTIME_BUNDLE_DIR ?= $(CURDIR)/dist/kandev
RUNTIME_VERSION ?= $(shell git describe --tags --always --dirty $(NULL_REDIR) || echo dev)
SERVICE_BUNDLE_DIR ?= $(CURDIR)/dist/kandev
SERVICE_LAUNCHER = $(SERVICE_BUNDLE_DIR)/bin/kandev
SERVICE_VERSION ?= $(RUNTIME_VERSION)
SERVICE_ENV = KANDEV_BUNDLE_DIR="$(SERVICE_BUNDLE_DIR)" KANDEV_VERSION="$(SERVICE_VERSION)"
PORT_FLAG := $(if $(PORT),--port $(PORT),)
SERVICE_HOME_DIR_FLAG := $(if $(HOME_DIR),--home-dir "$(HOME_DIR)",)
SERVICE_NO_BOOT_START_FLAG := $(if $(filter 1 true yes,$(NO_BOOT_START)),--no-boot-start,)
SERVICE_INSTALL_FLAGS := $(PORT_FLAG) $(SERVICE_HOME_DIR_FLAG) $(SERVICE_NO_BOOT_START_FLAG)
DEV_WEB_PORT_FLAG := $(if $(WEB_PORT),--web-internal-port $(WEB_PORT),)
DEV_FLAGS := $(PORT_FLAG) $(DEV_WEB_PORT_FLAG) $(DEV_ARGS)
# $(if …) does not strip its condition, so an all-whitespace DEV_FLAGS reads as
# true. Test and print this instead, and forward DEV_FLAGS itself — stripping
# what reaches the CLI would collapse whitespace inside a quoted DEV_ARGS value.
DEV_FLAGS_DISPLAY := $(strip $(DEV_FLAGS))
DESKTOP_BUNDLES ?= dmg
# Phase headers
define phase
@printf "\n$(BOLD)$(BLUE)━━━ $(1) ━━━$(RESET)\n\n"
endef
# Success message
define success
@printf "$(GREEN)✓$(RESET) $(1)\n"
endef
# Default target
.DEFAULT_GOAL := help
#
# Help
#
.PHONY: help
help:
@echo "Kandev - AI Agent Kanban Board"
@echo ""
@echo "Development Commands:"
@echo " bootstrap Install mise tools, workspace deps, and git hooks"
@echo " bootstrap-e2e Bootstrap plus Playwright browser/system deps"
@echo " dev Run backend + web via local CLI (auto ports)"
@echo " dev PORT=38430 WEB_PORT=37430 PORT beats KANDEV_BACKEND_PORT/KANDEV_PORT, WEB_PORT beats KANDEV_WEB_PORT"
@echo " dev DEV_ARGS='--verbose' Pass extra flags through to the dev CLI"
@echo " dev-prod-db Run dev mode against the production db at ~/.kandev"
@echo " dev-backend Run backend in development mode (port 38429)"
@echo " dev-web Run web app in development mode (port 37429)"
@echo " desktop-dev Run macOS Tauri app in dev mode with bundled runtime"
@echo " doctor Idempotently wire up pre-commit hooks (runs automatically before dev)"
@echo ""
@echo "Production Commands:"
@echo " start Install deps, build, and start backend + web in production mode"
@echo " start-verbose Start in production mode with info logs from backend + web"
@echo " start VERBOSE=1 Same as start-verbose"
@echo " start-windows cmd.exe-safe start for native Windows (no printf/find/cp/exec)"
@echo ""
@echo "Service Commands:"
@echo " service-install Install deps, build current checkout, install user service"
@echo " service-install-system Install deps, build current checkout, install system service"
@echo " service-status Show current user service status"
@echo " service-logs Show current user service logs"
@echo " service-logs-follow Follow current user service logs"
@echo " service-start Start current user service"
@echo " service-stop Stop current user service"
@echo " service-restart Restart current user service"
@echo " service-uninstall Uninstall current user service"
@echo " service-config Show service launcher/config paths"
@echo " service-install PORT=3000 HOME_DIR=/path Optional install overrides"
@echo " service-install NO_BOOT_START=1 Skip Linux user-service boot hint"
@echo ""
@echo "Build Commands:"
@echo " build Build backend and web app"
@echo " build-backend Build backend binary"
@echo " build-web Build web app for production"
@echo " runtime-bundle Build the package-manager runtime bundle (deps must exist)"
@echo " desktop-runtime Build/copy runtime resources for the macOS desktop app"
@echo " desktop-build Build the macOS Tauri app bundle/DMG"
@echo " desktop-open Build and open the macOS app"
@echo " desktop-launch Alias for desktop-open"
@echo ""
@echo "Installation:"
@echo " install Install all dependencies (backend + web)"
@echo " install-backend Install backend dependencies"
@echo " install-web Install web dependencies (uses pnpm workspace)"
@echo ""
@echo "Testing:"
@echo " test Run all tests (backend + web + cli)"
@echo " test-windows Run Windows-clean subset (curated backend + web + cli)"
@echo " test-backend Run backend tests"
@echo " test-web Run web app tests"
@echo " test-cli Run CLI tests"
@echo " test-e2e Run E2E tests (headless, parallel)"
@echo " test-e2e-headed Run E2E tests with visible browser"
@echo " test-e2e-ui Run E2E tests in Playwright UI mode"
@echo " test-e2e-ci Run E2E tests in Docker with CI-like Linux + resource limits"
@echo " test-e2e-report Open Playwright HTML report"
@echo ""
@echo "Code Quality:"
@echo " lint Run linters for both components"
@echo " lint-backend Run Go linters"
@echo " lint-web Run ESLint"
@echo " lint-architecture Enforce architecture budgets and compatibility expiry"
@echo " lint-format Check formatting with Prettier (web/cli/packages)"
@echo " dead-code-workspaces Find unused TypeScript workspace files, exports, and dependencies"
@echo " dead-code-go Find unreachable Go functions (host config; verify other targets before deletion)"
@echo " fmt Format all code"
@echo " fmt-backend Format Go code"
@echo " fmt-web Format web/cli/packages with Prettier, then ESLint --fix (web)"
@echo ""
@echo "Cleanup:"
@echo " clean Remove all build artifacts"
@echo " clean-backend Remove backend build artifacts"
@echo " clean-web Remove web build artifacts"
@echo " clean-db Remove local SQLite database"
#
# Development
#
.PHONY: bootstrap
bootstrap:
@scripts/bootstrap-dev-env
.PHONY: bootstrap-e2e
bootstrap-e2e:
@scripts/bootstrap-dev-env --with-e2e
.PHONY: doctor
doctor:
# Native Windows (cmd/PowerShell) has no POSIX shell for the bash-only doctor
# script. Under Git Bash/MSYS ($(MSYSTEM) is set) it runs fine, so skip only
# when OS is Windows_NT AND MSYSTEM is empty — the concatenation equals
# "Windows_NT" only in that native case.
ifeq ($(OS)$(MSYSTEM),Windows_NT)
@echo pre-commit hooks skipped on native Windows - run scripts/doctor from Git Bash to enable
else
@scripts/doctor
endif
.PHONY: dev
dev: doctor
@echo "Building remote agentctl helpers..."
@$(MAKE) -C $(BACKEND_DIR) build-agentctl-remote
ifeq ($(OS),Windows_NT)
@echo "Building winjob (Ctrl-C-safe wrapper for Windows)..."
@$(MAKE) -C $(BACKEND_DIR) build-winjob
endif
@echo "Launching via CLI$(if $(DEV_FLAGS_DISPLAY), ($(DEV_FLAGS_DISPLAY)), (auto ports))..."
@cd $(APPS_DIR) && $(PNPM) -C cli dev -- dev $(DEV_FLAGS)
.PHONY: dev-prod-db
dev-prod-db: export KANDEV_DATABASE_PATH := $(HOME)/.kandev/data/kandev.db
dev-prod-db:
@echo "⚠ dev mode against PRODUCTION db at $(KANDEV_DATABASE_PATH)"
@$(MAKE) dev
.PHONY: dev-backend
dev-backend:
@echo "Starting backend on http://localhost:38429"
@trap 'stty sane 2>/dev/null || true' EXIT INT TERM; \
$(MAKE) -C $(BACKEND_DIR) run; \
stty sane 2>/dev/null || true
.PHONY: dev-web
dev-web:
@echo "Starting web app on http://localhost:37429"
@cd $(APPS_DIR) && PORT=37429 $(PNPM) --filter @kandev/web dev
.PHONY: desktop-runtime
desktop-runtime:
@test "$$(uname -s)" = "Darwin" || { echo "desktop-* targets require macOS."; exit 1; }
@$(MAKE) -s service-bundle
@platform="$(DESKTOP_PLATFORM)"; \
if [ -z "$$platform" ]; then \
case "$$(uname -m)" in \
arm64|aarch64) platform="macos-arm64" ;; \
x86_64|amd64) platform="macos-x64" ;; \
*) echo "Unsupported macOS architecture: $$(uname -m)" >&2; exit 1 ;; \
esac; \
fi; \
scripts/release/prepare-desktop-runtime.sh \
--bundle-dir "$(SERVICE_BUNDLE_DIR)" \
--platform "$$platform" \
--output-dir "$(DESKTOP_RUNTIME_DIR)"
.PHONY: desktop-dev
desktop-dev: desktop-runtime
@KANDEV_DESKTOP_RUNTIME_DIR="$(CURDIR)/$(DESKTOP_RUNTIME_DIR)" \
$(PNPM) -C $(APPS_DIR) --filter @kandev/desktop dev
.PHONY: desktop-build
desktop-build: desktop-runtime
@cd $(DESKTOP_DIR) && $(PNPM) tauri build --features desktop-runtime --bundles "$(DESKTOP_BUNDLES)"
.PHONY: desktop-open
desktop-open: desktop-build
@app_path="$$(find "$(DESKTOP_DIR)/src-tauri/target" -path '*/release/bundle/macos/Kandev.app' -print -quit)"; \
if [ -z "$$app_path" ]; then \
echo "Missing built app under $(DESKTOP_DIR)/src-tauri/target"; \
exit 1; \
fi; \
open "$$app_path"
.PHONY: desktop-launch
desktop-launch: desktop-open
#
# Build
#
.PHONY: build
build: build-web sync-embedded-web build-backend
@printf "\n$(GREEN)$(BOLD)✓ Build complete!$(RESET)\n"
#
# Production Start
#
.PHONY: start
start:
$(call phase,Installing Dependencies)
@$(MAKE) -s install-backend
@$(MAKE) -s install-web
$(call success,Dependencies installed)
$(call phase,Building)
@$(MAKE) -s build-web-quiet
@$(MAKE) -s sync-embedded-web
@$(MAKE) -s build-backend-quiet
$(call success,Build complete)
$(call phase,Starting Server)
@exec $(BACKEND_DIR)/bin/kandev start $(if $(filter 1 true yes,$(VERBOSE)),--verbose,) $(if $(filter 1 true yes,$(DEBUG)),--debug,)
.PHONY: start-verbose
start-verbose:
@$(MAKE) start VERBOSE=1
.PHONY: start-debug
start-debug:
@$(MAKE) start DEBUG=1
# Windows-native production start. Mirrors `start` but avoids the Unix-only
# tooling that target relies on (printf, find, cp, exec) and would break under
# cmd.exe. Run it from a shell where GNU Make invokes cmd.exe — the default on
# Windows when sh.exe is not on PATH — because the backend build uses cmd's
# `set VAR=VAL&` env-var syntax. Skips the Playwright browser install (only
# needed for e2e, not to run the server).
.PHONY: start-windows
start-windows:
@echo Installing backend dependencies...
@$(MAKE) -s -C $(BACKEND_DIR) deps
@echo Installing web dependencies...
@cd $(APPS_DIR) && $(PNPM) install
@echo Building web app...
@cd $(APPS_DIR) && set "VITE_KANDEV_API_PORT=" && set "VITE_KANDEV_DEBUG=" && $(PNPM) --filter @kandev/web build
@$(MAKE) -s sync-embedded-web-windows
@echo Building backend...
@$(MAKE) -s -C $(BACKEND_DIR) build
@echo Starting server...
@"$(subst /,\,$(BACKEND_DIR)/bin/kandev.exe)" start $(if $(filter 1 true yes,$(VERBOSE)),--verbose,) $(if $(filter 1 true yes,$(DEBUG)),--debug,)
# Windows-native counterparts of start-verbose / start-debug. Same cmd.exe
# constraints as start-windows.
.PHONY: start-windows-verbose
start-windows-verbose:
@$(MAKE) start-windows VERBOSE=1
.PHONY: start-windows-debug
start-windows-debug:
@$(MAKE) start-windows DEBUG=1
#
# Service
#
.PHONY: runtime-bundle
runtime-bundle:
$(call phase,Packaging Runtime Bundle)
@test -n "$(RUNTIME_BUNDLE_DIR)" || { echo "RUNTIME_BUNDLE_DIR is empty; aborting."; exit 1; }
@test "$(RUNTIME_BUNDLE_DIR)" != "/" || { echo "RUNTIME_BUNDLE_DIR must not be /; aborting."; exit 1; }
@$(MAKE) -s build-web
@$(MAKE) -s sync-embedded-web
@$(MAKE) -C $(BACKEND_DIR) build-runtime VERSION="$(RUNTIME_VERSION)" GOFLAGS="$(GOFLAGS)"
@set -eu; \
requested_bundle_dir="$(RUNTIME_BUNDLE_DIR)"; \
mkdir -p "$$requested_bundle_dir"; \
resolved_bundle_dir="$$(cd "$$requested_bundle_dir" && pwd -P)"; \
test -n "$$resolved_bundle_dir" || { echo "RUNTIME_BUNDLE_DIR could not be resolved; aborting."; exit 1; }; \
test "$$resolved_bundle_dir" != "/" || { echo "RUNTIME_BUNDLE_DIR must not resolve to /; aborting."; exit 1; }; \
staging_bundle_dir="$$(mktemp -d "$$resolved_bundle_dir/.runtime-bundle.XXXXXX")"; \
trap 'rm -rf "$$staging_bundle_dir"' EXIT; \
mkdir -p "$$staging_bundle_dir/bin"; \
cp "$(BACKEND_DIR)/bin/kandev" "$(BACKEND_DIR)/bin/agentctl" \
"$(BACKEND_DIR)/bin/agentctl-linux-amd64" \
"$(BACKEND_DIR)/bin/agentctl-linux-arm64" \
"$(BACKEND_DIR)/bin/agentctl-darwin-arm64" \
"$(BACKEND_DIR)/bin/agentctl-darwin-amd64" \
"$$staging_bundle_dir/bin/"; \
scripts/release/package-bundle.sh --bundle-dir "$$staging_bundle_dir"; \
rm -rf "$$resolved_bundle_dir/bin"; \
mv "$$staging_bundle_dir/bin" "$$resolved_bundle_dir/bin"; \
rmdir "$$staging_bundle_dir"; \
trap - EXIT
$(call success,Runtime bundle packaged at $(RUNTIME_BUNDLE_DIR))
.PHONY: service-bundle
service-bundle: install
@$(MAKE) -s runtime-bundle \
RUNTIME_BUNDLE_DIR="$(SERVICE_BUNDLE_DIR)" \
RUNTIME_VERSION="$(SERVICE_VERSION)"
.PHONY: service-cli-check
service-cli-check:
@test -f "$(SERVICE_LAUNCHER)" || { echo "Missing $(SERVICE_LAUNCHER). Run 'make service-install' first."; exit 1; }
.PHONY: service-install
service-install: service-bundle
@$(SERVICE_ENV) "$(SERVICE_LAUNCHER)" service install $(SERVICE_INSTALL_FLAGS)
.PHONY: service-install-system
service-install-system: service-bundle
@sudo env $(SERVICE_ENV) "$(SERVICE_LAUNCHER)" service install --system $(SERVICE_INSTALL_FLAGS)
.PHONY: service-uninstall service-start service-stop service-restart service-status service-logs service-logs-follow service-config
service-uninstall: service-cli-check
@$(SERVICE_ENV) "$(SERVICE_LAUNCHER)" service uninstall
service-start: service-cli-check
@$(SERVICE_ENV) "$(SERVICE_LAUNCHER)" service start
service-stop: service-cli-check
@$(SERVICE_ENV) "$(SERVICE_LAUNCHER)" service stop
service-restart: service-cli-check
@$(SERVICE_ENV) "$(SERVICE_LAUNCHER)" service restart
service-status: service-cli-check
@$(SERVICE_ENV) "$(SERVICE_LAUNCHER)" service status
service-logs: service-cli-check
@$(SERVICE_ENV) "$(SERVICE_LAUNCHER)" service logs
service-logs-follow: service-cli-check
@$(SERVICE_ENV) "$(SERVICE_LAUNCHER)" service logs -f
service-config: service-cli-check
@$(SERVICE_ENV) "$(SERVICE_LAUNCHER)" service config
.PHONY: build-backend
build-backend:
@printf "$(CYAN)Building backend...$(RESET)\n"
@$(MAKE) -C $(BACKEND_DIR) build
.PHONY: build-backend-remote-helpers build-backend-linux-helpers
build-backend-remote-helpers:
@printf "$(CYAN)Building remote helper binaries (agentctl helpers + mock-agent) for executor E2E...$(RESET)\n"
@$(MAKE) -C $(BACKEND_DIR) build-agentctl-remote build-mock-agent-linux
build-backend-linux-helpers: build-backend-remote-helpers
.PHONY: acpdbg
acpdbg:
@$(MAKE) -s -C $(BACKEND_DIR) acpdbg ARGS="$(ARGS)"
.PHONY: build-backend-quiet
build-backend-quiet:
@printf " $(DIM)Backend$(RESET)\n"
@$(MAKE) -s -C $(BACKEND_DIR) build >/dev/null 2>&1
## Package the plugin-fixture SDK plugin used by tests/plugins/plugins.spec.ts.
## e2e/global-setup.ts checks the resulting tar.gz exists (like it does for
## the kandev/mock-agent binaries) but does not build it itself — this target
## is the "make it exist" step, wired into test-e2e* below.
.PHONY: build-e2e-plugin-package
build-e2e-plugin-package:
@printf "$(CYAN)Packaging e2e fixture plugin...$(RESET)\n"
@$(MAKE) -C $(BACKEND_DIR) e2e-plugin-package
.PHONY: build-web
build-web:
@printf "$(CYAN)Building web app...$(RESET)\n"
@cd $(APPS_DIR) && VITE_KANDEV_API_PORT= VITE_KANDEV_DEBUG= $(PNPM) --filter @kandev/web build
## Web build for the E2E harness. Identical to build-web except that it keeps the
## pseudo QA catalog, which a production build drops (see
## apps/web/lib/i18n/bundling.ts). e2e/tests/i18n/pseudo-coverage.spec.ts is the
## only oracle for copy the jsx-only eslint guard cannot see, and it needs the
## catalog present in the artifact it runs against.
.PHONY: build-web-e2e
build-web-e2e:
@printf "$(CYAN)Building web app (with the pseudo QA locale)...$(RESET)\n"
@cd $(APPS_DIR) && VITE_KANDEV_API_PORT= VITE_KANDEV_DEBUG= $(PNPM) --filter @kandev/web build:e2e
.PHONY: build-web-quiet
build-web-quiet:
@printf " $(DIM)Web app$(RESET)\n"
@cd $(APPS_DIR) && VITE_KANDEV_API_PORT= VITE_KANDEV_DEBUG= $(PNPM) --filter @kandev/web build 2>&1 | grep -v "Warning:" | grep -v "parseLineType" | grep -v "^$$" || true
.PHONY: sync-embedded-web
sync-embedded-web:
@test -f "$(WEB_DIR)/dist/index.html" || { echo "Missing $(WEB_DIR)/dist/index.html; run 'make build-web' first."; exit 1; }
@mkdir -p "$(EMBEDDED_WEB_DIR)"
@find "$(EMBEDDED_WEB_DIR)" -mindepth 1 ! -name .gitignore ! -name keep.txt -exec rm -rf {} +
@cp -R "$(WEB_DIR)/dist/." "$(EMBEDDED_WEB_DIR)/"
@printf " $(DIM)Embedded web assets$(RESET)\n"
# cmd.exe-safe counterpart of sync-embedded-web (used by start-windows).
# robocopy /MIR mirrors the Vite dist into the embedded dir; /XF keeps the
# committed .gitignore and keep.txt while purging stale generated assets.
# robocopy exit codes below 8 all mean success, so normalize them to 0 —
# otherwise make reads robocopy's "files copied" code (1) as a failure.
.PHONY: sync-embedded-web-windows
sync-embedded-web-windows:
@if not exist "$(subst /,\,$(WEB_DIR)/dist/index.html)" (echo Missing $(WEB_DIR)/dist/index.html - run 'make start-windows' to build everything. & exit /b 1)
@robocopy "$(subst /,\,$(WEB_DIR)/dist)" "$(subst /,\,$(EMBEDDED_WEB_DIR))" /MIR /XF .gitignore keep.txt /NFL /NDL /NJH /NJS /NC /NS >nul & if errorlevel 8 (exit /b 1) else (exit /b 0)
@echo Embedded web assets synced.
#
# Installation
#
.PHONY: install
install: install-backend install-web
@printf "\n$(GREEN)$(BOLD)✓ All dependencies installed!$(RESET)\n"
.PHONY: install-backend
install-backend:
@printf "$(CYAN)Installing backend dependencies...$(RESET)\n"
@$(MAKE) -s -C $(BACKEND_DIR) deps
.PHONY: install-web
install-web:
@printf "$(CYAN)Installing web dependencies...$(RESET)\n"
@(cd $(APPS_DIR) && $(PNPM) install --silent 2>/dev/null) || (cd $(APPS_DIR) && $(PNPM) install)
@printf "$(CYAN)Installing Playwright browsers...$(RESET)\n"
@cd $(APPS_DIR) && $(PNPM) --filter @kandev/web exec playwright install chromium
#
# Testing
#
.PHONY: test
test: test-backend test-web test-cli test-scripts
@printf "\n$(GREEN)$(BOLD)✓ All tests complete!$(RESET)\n"
# Curated Windows-clean test run. Mirrors the test-windows job in
# .github/workflows/backend-tests.yml: the backend portion skips ~24 tests
# with Unix-only fixtures (sleep/cat/echo in test inputs, POSIX symlinks,
# delete-while-open). Web and CLI use vitest, which is cross-platform.
# Shrink the backend skip list as fixtures get cleaned up.
#
# Deliberately uses plain `echo` and inlines pnpm invocations (rather than
# depending on test-backend/test-web/test-cli) so it does not pull in the
# `@printf` and `$(shell uname ...)` calls used by other targets — those
# fail on cmd.exe (no printf.exe, no uname.exe) and would break the run on
# Windows even though they are cosmetic on Unix.
.PHONY: test-windows
test-windows:
@echo "[backend] Running Windows-clean subset..."
@$(MAKE) -C $(BACKEND_DIR) test-windows
@echo "[web] Running tests..."
@cd $(APPS_DIR) && $(PNPM) --filter @kandev/web test
@echo "[cli] Running tests..."
@cd $(APPS_DIR) && $(PNPM) --filter kandev test
@echo "Windows-clean test subset complete."
.PHONY: test-sprites-e2e
test-sprites-e2e:
@$(MAKE) -C $(BACKEND_DIR) test-sprites-e2e
.PHONY: test-backend
test-backend:
@printf "$(CYAN)Running backend tests...$(RESET)\n"
@$(MAKE) -C $(BACKEND_DIR) test
.PHONY: test-web
test-web:
@printf "$(CYAN)Running web app tests...$(RESET)\n"
@cd $(APPS_DIR) && $(PNPM) --filter @kandev/web test
.PHONY: test-cli
test-cli:
@printf "$(CYAN)Running CLI tests...$(RESET)\n"
@cd $(APPS_DIR) && $(PNPM) --filter kandev test
.PHONY: test-scripts
test-scripts:
@printf "$(CYAN)Running script tests...$(RESET)\n"
@python3 .github/scripts/lint-action-pinning_test.py
@bash scripts/pr-state.test.sh
@bash scripts/run-quiet.test.sh
@bash scripts/opencode-code-review.test.sh
@python3 scripts/opencode-code-review.test.py
@python3 scripts/lint-harness-files.test.py
@python3 scripts/lint-architecture.test.py
@bash scripts/release-desktop.test.sh
@bash scripts/release/runtime-bundle.test.sh
@node --test apps/desktop/e2e/desktop-launch-smoke.test.mjs
@python3 .github/scripts/release-workflow-contract_test.py
@node --test scripts/release/nightly-version.test.mjs scripts/release/nightly-release.test.mjs scripts/release/npm-view-version.test.mjs scripts/release/publish-npm.test.mjs
@node --test scripts/validate-public-docs.test.mjs
.PHONY: test-e2e
test-e2e: build-backend build-web-e2e build-e2e-plugin-package
@printf "$(CYAN)Running E2E tests (headless, parallel, managed runner)...$(RESET)\n"
@cd $(WEB_DIR) && status=0; for project in routing auth chromium mobile-chrome containers; do \
printf "$(CYAN)-- project: $$project --$(RESET)\n"; \
e2e/scripts/run-e2e.sh --host --no-build --no-strict --shards 1 --project "$$project" -- --output="e2e/test-results-$$project" || status=1; \
done; exit $$status
.PHONY: test-e2e-headed
test-e2e-headed: build-backend build-web-e2e build-e2e-plugin-package
@printf "$(CYAN)Running E2E tests (headed)...$(RESET)\n"
@cd $(APPS_DIR) && $(PNPM) --filter @kandev/web e2e:headed
.PHONY: test-e2e-ui
test-e2e-ui: build-backend build-web-e2e build-e2e-plugin-package
@printf "$(CYAN)Opening Playwright UI mode...$(RESET)\n"
@cd $(APPS_DIR) && $(PNPM) --filter @kandev/web e2e:ui
.PHONY: test-e2e-report
test-e2e-report:
@cd $(WEB_DIR) && npx playwright show-report e2e/playwright-report
# Run E2E tests inside Docker to simulate CI conditions (Linux + resource limits).
# Configurable via env vars; defaults match GitHub Actions ubuntu-latest runners.
E2E_CI_CPUS ?= 4
E2E_CI_MEMORY ?= 16g
E2E_CI_SHM_SIZE ?= 1g
E2E_CI_IMAGE ?= kandev-e2e
.PHONY: test-e2e-ci
test-e2e-ci:
@printf "$(CYAN)Building E2E Docker image...$(RESET)\n"
@docker build -f e2e.Dockerfile -t $(E2E_CI_IMAGE) .
@printf "$(CYAN)Running E2E tests in Docker (cpus=$(E2E_CI_CPUS), memory=$(E2E_CI_MEMORY))...$(RESET)\n"
@docker run --rm \
--cpus=$(E2E_CI_CPUS) \
--memory=$(E2E_CI_MEMORY) \
--shm-size=$(E2E_CI_SHM_SIZE) \
$(E2E_CI_IMAGE) $(E2E_ARGS)
#
# Code Quality
#
.PHONY: lint
lint: lint-backend lint-web lint-harness lint-architecture
@printf "\n$(GREEN)$(BOLD)✓ Linting complete!$(RESET)\n"
.PHONY: lint-backend
lint-backend:
@printf "$(CYAN)Linting backend...$(RESET)\n"
@$(MAKE) -C $(BACKEND_DIR) lint
.PHONY: lint-web
lint-web:
@printf "$(CYAN)Linting web app...$(RESET)\n"
@cd $(APPS_DIR) && $(PNPM) --filter @kandev/web lint
.PHONY: lint-harness
lint-harness:
@printf "$(CYAN)Linting harness files...$(RESET)\n"
@python3 .github/scripts/lint-harness-files.py --all
.PHONY: lint-architecture
lint-architecture:
@printf "$(CYAN)Linting architecture...$(RESET)\n"
@python3 scripts/lint-architecture.py --all
.PHONY: lint-format
lint-format:
@printf "$(CYAN)Checking formatting...$(RESET)\n"
@cd $(APPS_DIR) && $(PNPM) run format:check
.PHONY: dead-code-workspaces
dead-code-workspaces:
@printf "$(CYAN)Auditing TypeScript workspace dead code...$(RESET)\n"
@cd $(APPS_DIR) && $(PNPM) run dead-code
.PHONY: dead-code-go
dead-code-go:
@printf "$(CYAN)Auditing Go dead code...$(RESET)\n"
@$(MAKE) -C $(BACKEND_DIR) deadcode
.PHONY: fmt
fmt: fmt-backend fmt-web
@printf "\n$(GREEN)$(BOLD)✓ Code formatting complete!$(RESET)\n"
.PHONY: fmt-backend
fmt-backend:
@printf "$(CYAN)Formatting backend code...$(RESET)\n"
@$(MAKE) -C $(BACKEND_DIR) fmt
.PHONY: fmt-web
fmt-web:
@printf "$(CYAN)Formatting web code...$(RESET)\n"
@cd $(APPS_DIR) && $(PNPM) run format
.PHONY: typecheck-web
typecheck-web:
@printf "$(CYAN)Type-checking web app...$(RESET)\n"
@cd $(APPS_DIR) && $(PNPM) --filter @kandev/web exec tsc -p tsconfig.json --noEmit
.PHONY: typecheck
typecheck:
@printf "$(CYAN)Type-checking all apps...$(RESET)\n"
@cd $(APPS_DIR) && $(PNPM) -r exec tsc -p tsconfig.json --noEmit
#
# Cleanup
#
.PHONY: clean
clean: clean-backend clean-web
@printf "\n$(GREEN)$(BOLD)✓ Cleanup complete!$(RESET)\n"
.PHONY: clean-backend
clean-backend:
@printf "$(CYAN)Cleaning backend artifacts...$(RESET)\n"
@$(MAKE) -C $(BACKEND_DIR) clean
.PHONY: clean-web
clean-web:
@printf "$(CYAN)Cleaning web artifacts...$(RESET)\n"
@$(RMDIR) $(WEB_DIR)/dist $(WEB_DIR)/.next $(APPS_DIR)/node_modules
@$(RMDIR) $(APPS_DIR)/packages/*/node_modules
.PHONY: clean-db
clean-db:
@printf "$(CYAN)Removing dev database (.kandev-dev/)...$(RESET)\n"
@$(RMDIR) .kandev-dev