.PHONY: all dev build test lint clean \ version-sync version-check \ backend-build backend-run backend-dev backend-test backend-test-all backend-test-unit backend-test-integration backend-test-coverage \ backend-lint backend-clean backend-deps backend-generate \ backend-db-up backend-db-down backend-db-status backend-db-create \ test-mysql-up test-mysql-down test-mysql test-mysql-quick \ frontend-build frontend-dev frontend-test frontend-test-watch frontend-test-coverage frontend-test-e2e frontend-lint frontend-clean \ desktop-build desktop-build-mac desktop-build-win desktop-build-linux \ desktop-dev desktop-test desktop-clean \ desktop-prepare-frontend desktop-prepare-embedfs desktop-prepare-windows-resource \ release-assets-linux release-assets-windows release-assets-macos VERSION := $(shell go run ./backend/cmd/versionctl print) GIT_COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || printf 'unknown') BUILD_TIME ?= $(shell date -u +"%Y-%m-%dT%H:%M:%SZ") GO_LDFLAGS := -X nex/backend/pkg/buildinfo.version=$(VERSION) -X nex/backend/pkg/buildinfo.commit=$(GIT_COMMIT) -X nex/backend/pkg/buildinfo.buildTime=$(BUILD_TIME) GO_LDFLAGS_WIN := $(GO_LDFLAGS) -H=windowsgui RELEASE_DIR := build/release SERVER_LINUX_ASSET := $(shell go run ./backend/cmd/versionctl asset-name server linux amd64) SERVER_WINDOWS_ASSET := $(shell go run ./backend/cmd/versionctl asset-name server windows amd64) SERVER_DARWIN_AMD64_ASSET := $(shell go run ./backend/cmd/versionctl asset-name server darwin amd64) SERVER_DARWIN_ARM64_ASSET := $(shell go run ./backend/cmd/versionctl asset-name server darwin arm64) DESKTOP_LINUX_ASSET := $(shell go run ./backend/cmd/versionctl asset-name desktop linux) DESKTOP_WINDOWS_ASSET := $(shell go run ./backend/cmd/versionctl asset-name desktop windows) DESKTOP_MACOS_ASSET := $(shell go run ./backend/cmd/versionctl asset-name desktop macos) # ============================================ # 顶层便捷命令 # ============================================ dev: @echo "🚀 Starting development environment..." @$(MAKE) -j2 backend-dev frontend-dev build: backend-build frontend-build @echo "✅ Build complete" test: backend-test desktop-test frontend-test @echo "✅ All tests passed" lint: backend-lint frontend-lint @echo "✅ Lint complete" all: build test lint # ============================================ # 版本管理 # ============================================ version-sync: go run ./backend/cmd/versionctl sync version-check: go run ./backend/cmd/versionctl check # ============================================ # 后端 # ============================================ backend-build: version-check cd backend && go build -ldflags "$(GO_LDFLAGS)" -o bin/server ./cmd/server backend-run: version-check cd backend && go run -ldflags "$(GO_LDFLAGS)" ./cmd/server backend-dev: version-check cd backend && go run -ldflags "$(GO_LDFLAGS)" ./cmd/server backend-test: cd backend && go test ./internal/... ./pkg/... ./tests/... ./cmd/server/... -v backend-test-all: cd backend && go test ./... -v backend-test-unit: cd backend && go test ./internal/... ./pkg/... -v backend-test-integration: cd backend && go test ./tests/... -v backend-test-coverage: cd backend && go test ./... -coverprofile=coverage.out cd backend && go tool cover -html=coverage.out -o coverage.html @echo "Coverage report generated: backend/coverage.html" backend-lint: cd backend && go tool golangci-lint run ./... backend-clean: rm -rf backend/bin/ backend/coverage.out backend/coverage.html backend-deps: cd backend && go mod tidy backend-generate: cd backend && go generate ./... backend-db-up: @echo "Running database migration up..." cd backend && goose -dir migrations/sqlite sqlite3 "$(DB_PATH)" up backend-db-down: @echo "Running database migration down..." cd backend && goose -dir migrations/sqlite sqlite3 "$(DB_PATH)" down backend-db-status: @echo "Checking database migration status..." cd backend && goose -dir migrations/sqlite sqlite3 "$(DB_PATH)" status backend-db-create: @read -p "Migration name: " name; \ cd backend && goose -dir migrations/sqlite create $$name sql; \ cd backend && goose -dir migrations/mysql create $$name sql # ============================================ # MySQL 专项测试 # ============================================ test-mysql-up: @echo "Starting MySQL test container..." cd backend/tests/mysql && docker-compose up -d @echo "Waiting for MySQL to be ready..." @for i in $$(seq 1 30); do \ if docker exec nex-mysql-test mysqladmin ping -h localhost -u root -ptestpass --silent 2>/dev/null; then \ echo "MySQL is ready!"; \ exit 0; \ fi; \ echo "Waiting... ($$i/30)"; \ sleep 1; \ done; \ echo "MySQL failed to start"; \ exit 1 test-mysql-down: @echo "Stopping MySQL test container..." cd backend/tests/mysql && docker-compose down -v test-mysql: test-mysql-up @echo "Running MySQL tests..." cd backend && go test -tags=mysql ./tests/mysql/... -v -count=1 $(MAKE) test-mysql-down test-mysql-quick: @echo "Running MySQL tests (without container management)..." cd backend && go test -tags=mysql ./tests/mysql/... -v -count=1 # ============================================ # 前端 # ============================================ frontend-install: cd frontend && bun install frontend-build: frontend-install version-sync cd frontend && bun run build frontend-dev: frontend-install version-sync cd frontend && bun dev frontend-test: frontend-install cd frontend && bun run test frontend-test-watch: frontend-install cd frontend && bun run test:watch frontend-test-coverage: frontend-install cd frontend && bun run test:coverage frontend-test-e2e: frontend-install cd frontend && bun run test:e2e frontend-lint: frontend-install cd frontend && bun run lint frontend-clean: rm -rf frontend/dist frontend/.next frontend/node_modules frontend/coverage frontend/playwright-report frontend/test-results frontend/tsconfig.tsbuildinfo # ============================================ # 桌面应用 # ============================================ desktop-build: desktop-build-mac desktop-build-win desktop-build-linux @echo "✅ Desktop builds complete for all platforms" desktop-prepare-frontend: frontend-install version-sync @echo "📦 Preparing frontend for desktop..." ifeq ($(OS),Windows_NT) powershell -NoProfile -Command "Copy-Item -LiteralPath 'frontend/.env.desktop' -Destination 'frontend/.env.production.local' -Force" cd frontend && bun run build powershell -NoProfile -Command "Remove-Item -LiteralPath 'frontend/.env.production.local' -Force -ErrorAction SilentlyContinue" else cd frontend && cp .env.desktop .env.production.local cd frontend && bun run build rm -f frontend/.env.production.local endif desktop-prepare-embedfs: @echo "📦 Preparing embedded filesystem..." ifeq ($(OS),Windows_NT) powershell -NoProfile -Command "Remove-Item -LiteralPath 'embedfs/assets' -Recurse -Force -ErrorAction SilentlyContinue; Remove-Item -LiteralPath 'embedfs/frontend-dist' -Recurse -Force -ErrorAction SilentlyContinue; Copy-Item -LiteralPath 'assets' -Destination 'embedfs/assets' -Recurse; Copy-Item -LiteralPath 'frontend/dist' -Destination 'embedfs/frontend-dist' -Recurse" else rm -rf embedfs/assets embedfs/frontend-dist cp -r assets embedfs/assets cp -r frontend/dist embedfs/frontend-dist endif desktop-prepare-windows-resource: @echo "📦 Preparing Windows executable icon..." ifeq ($(OS),Windows_NT) cd backend/cmd/desktop && windres -O coff -F pe-x86-64 -i icon_windows.rc -o rsrc_windows_amd64.syso else @if command -v x86_64-w64-mingw32-windres >/dev/null 2>&1; then \ cd backend/cmd/desktop && x86_64-w64-mingw32-windres -O coff -F pe-x86-64 -i icon_windows.rc -o rsrc_windows_amd64.syso; \ elif command -v windres >/dev/null 2>&1; then \ cd backend/cmd/desktop && windres -O coff -F pe-x86-64 -i icon_windows.rc -o rsrc_windows_amd64.syso; \ else \ echo "❌ 未找到 windres,无法生成 Windows exe 图标资源"; \ exit 1; \ fi endif desktop-build-mac: desktop-prepare-frontend desktop-prepare-embedfs @echo "🍎 Building macOS..." cd backend && CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build -ldflags "$(GO_LDFLAGS)" -o ../build/nex-mac-arm64 ./cmd/desktop cd backend && CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build -ldflags "$(GO_LDFLAGS)" -o ../build/nex-mac-amd64 ./cmd/desktop lipo -create build/nex-mac-arm64 build/nex-mac-amd64 -output build/nex-mac-universal @echo "📦 Packaging macOS .app..." mkdir -p build/Nex.app/Contents/MacOS build/Nex.app/Contents/Resources cp build/nex-mac-universal build/Nex.app/Contents/MacOS/nex @if [ -f assets/icon.icns ]; then \ cp assets/icon.icns build/Nex.app/Contents/Resources/; \ else \ echo "⚠️ 未找到 assets/icon.icns"; \ fi @MIN_MACOS_VERSION=$$(vtool -show-build build/nex-mac-universal | awk '/minos / {print $$2; exit}'); \ if [ -z "$$MIN_MACOS_VERSION" ]; then \ echo "❌ 无法读取 macOS 最低系统版本"; \ exit 1; \ fi; \ go run ./backend/cmd/versionctl macos-plist "$$MIN_MACOS_VERSION" > build/Nex.app/Contents/Info.plist chmod +x build/Nex.app/Contents/MacOS/nex @echo "✅ macOS app packaged: build/Nex.app" desktop-build-win: desktop-prepare-frontend desktop-prepare-embedfs desktop-prepare-windows-resource @echo "🪟 Building Windows..." ifeq ($(OS),Windows_NT) powershell -NoProfile -Command "New-Item -ItemType Directory -Path 'build' -Force | Out-Null" cd backend && set "CGO_ENABLED=1"&& set "GOOS=windows"&& set "GOARCH=amd64"&& go build -ldflags "$(GO_LDFLAGS_WIN)" -o ../build/nex-win-amd64.exe ./cmd/desktop else mkdir -p build cd backend && CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -ldflags "$(GO_LDFLAGS_WIN)" -o ../build/nex-win-amd64.exe ./cmd/desktop endif desktop-build-linux: desktop-prepare-frontend desktop-prepare-embedfs @echo "🐧 Building Linux..." cd backend && CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -ldflags "$(GO_LDFLAGS)" -o ../build/nex-linux-amd64 ./cmd/desktop desktop-dev: desktop-prepare-frontend desktop-prepare-embedfs @echo "🖥️ Starting desktop app in dev mode..." cd backend && go run -ldflags "$(GO_LDFLAGS)" ./cmd/desktop # ============================================ # 发布资产 # ============================================ release-assets-linux: version-sync version-check desktop-build-linux rm -rf "$(RELEASE_DIR)" mkdir -p "$(RELEASE_DIR)" cd backend && CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -ldflags "$(GO_LDFLAGS)" -o ../build/nex-server-linux-amd64 ./cmd/server tar -C build -czf "$(RELEASE_DIR)/$(SERVER_LINUX_ASSET)" nex-server-linux-amd64 tar -C build -czf "$(RELEASE_DIR)/$(DESKTOP_LINUX_ASSET)" nex-linux-amd64 release-assets-windows: version-sync version-check desktop-build-win ifeq ($(OS),Windows_NT) powershell -NoProfile -Command "Remove-Item -LiteralPath '$(RELEASE_DIR)' -Recurse -Force -ErrorAction SilentlyContinue; New-Item -ItemType Directory -Path '$(RELEASE_DIR)' -Force | Out-Null" cd backend && set "CGO_ENABLED=1"&& set "GOOS=windows"&& set "GOARCH=amd64"&& go build -ldflags "$(GO_LDFLAGS_WIN)" -o ../build/nex-server-win-amd64.exe ./cmd/server powershell -NoProfile -Command "Compress-Archive -LiteralPath 'build/nex-server-win-amd64.exe' -DestinationPath '$(RELEASE_DIR)/$(SERVER_WINDOWS_ASSET)' -Force" powershell -NoProfile -Command "Compress-Archive -LiteralPath 'build/nex-win-amd64.exe' -DestinationPath '$(RELEASE_DIR)/$(DESKTOP_WINDOWS_ASSET)' -Force" else @echo "❌ release-assets-windows 需要在 Windows 环境执行" @exit 1 endif release-assets-macos: version-sync version-check desktop-build-mac rm -rf "$(RELEASE_DIR)" mkdir -p "$(RELEASE_DIR)" cd backend && CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build -ldflags "$(GO_LDFLAGS)" -o ../build/nex-server-darwin-amd64 ./cmd/server cd backend && CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build -ldflags "$(GO_LDFLAGS)" -o ../build/nex-server-darwin-arm64 ./cmd/server tar -C build -czf "$(RELEASE_DIR)/$(SERVER_DARWIN_AMD64_ASSET)" nex-server-darwin-amd64 tar -C build -czf "$(RELEASE_DIR)/$(SERVER_DARWIN_ARM64_ASSET)" nex-server-darwin-arm64 ditto -c -k --keepParent build/Nex.app "$(RELEASE_DIR)/$(DESKTOP_MACOS_ASSET)" desktop-test: cd backend && go test ./cmd/desktop/... -v desktop-clean: rm -rf build/ embedfs/assets embedfs/frontend-dist # ============================================ # 清理 # ============================================ clean: backend-clean frontend-clean desktop-clean @echo "✅ Clean complete"