Ctrl K
ComunidadGuías…

Makefile

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
.PHONY: help build run run-api run-api-dev test test-short test-race test-verbose bench clean clean-all deps install web-dev web-build web-preview web-install web-clean web-restore-placeholder web-test
.PHONY: coverage coverage-fast coverage-html coverage-check coverage-pkg coverage-patch coverage-patch-check coverage-func ci ci-full config-drift config-sync check-import-guard check-mocks i18n-check simulate-ci
.PHONY: fmt lint vet vuln swagger docs mocks test-e2e-fullstack test-e2e-review-load test-e2e-frontend test-static-e2e test-e2e-field-drop test-e2e-cli test-e2e-live test-coverage
.PHONY: build-cli-linux build-cli-darwin build-cli-windows build-cli-all
.PHONY: build-app-darwin build-app-local build-app-windows build-app-linux build-app-all
.PHONY: act-list act-test act-build act-lint act-docker act-cli-release act-ci act-dry act-help
.PHONY: docker-build docker-build-no-cache docker-run docker-stop docker-restart docker-clean docker-push docker-test docker-logs docker-help
.PHONY: docker-compose-up docker-compose-down docker-compose-restart docker-compose-logs docker-compose-build

# Default target - show help
.DEFAULT_GOAL := help

# Help target - displays available targets
help:
	@echo "Javinizer Makefile - Available Targets"
	@echo ""
	@echo "Build & Run:"
	@echo "  make build              - Build single binary (API + embedded Web UI)"
	@echo "  make run                - Run CLI directly (no build)"
	@echo "  make run-api            - Run API server directly"
	@echo "  make run-api-dev        - Run API server with hot reload (air)"
	@echo "  make install            - Install binary to GOPATH/bin"
	@echo ""
	@echo "Testing:"
	@echo "  make test               - Run all tests with verbose output"
	@echo "  make test-short         - Run fast tests only (for pre-commit)"
	@echo "  make test-race          - Run race detector on concurrent packages"
	@echo "  make test-verbose       - Run tests with verbose output and count=1"
	@echo "  make bench              - Run benchmarks"
	@echo ""
	@echo "Coverage:"
	@echo "  make coverage           - Run tests and display line coverage summary"
	@echo "  make coverage-html      - Generate HTML coverage report (coverage.html)"
	@echo "  make coverage-pkg       - Display per-package coverage breakdown"
	@echo "  make coverage-check     - Enforce 75%% Codecov-compatible line coverage"
	@echo "  make coverage-patch     - Show patch coverage (new/changed lines vs main)"
	@echo "  make coverage-patch-check - Enforce 80%% patch coverage (mirrors codecov/patch)"
	@echo "  make coverage-patch-strict - Enforce 100%% patch coverage (PR completion bar)"
	@echo ""
	@echo "Code Quality:"
	@echo "  make fmt                - Format code with go fmt"
	@echo "  make vet                - Run go vet"
	@echo "  make lint               - Run golangci-lint"
	@echo "  make vuln               - Run govulncheck vulnerability scan"
	@echo "  make swagger            - Generate Swagger API documentation"
	@echo "  make check-swagger      - Check if Swagger docs are up to date"
	@echo "  make check-mocks        - Check if mockery mocks are up to date"
	@echo "  make mocks              - Generate mocks from interfaces (mockery v3)"
	@echo ""
	@echo "CI/CD:"
	@echo "  make ci                 - Run full CI suite (vet + lint + coverage + race + drift)"
	@echo "  make ci-full            - Run full CI suite including frontend tests"
	@echo "  make config-drift       - Validate config synchronization (no hardcoded values)"
	@echo "  make config-sync        - Sync configs/config.yaml.example from the embedded master copy"
	@echo "  make check-import-guard - Enforce models→config dependency direction"
	@echo "  make i18n-check         - Validate i18n catalogs (missing keys, placeholders, coverage)"
	@echo "  make simulate-ci        - Simulate GitHub Actions CI locally"
	@echo ""
	@echo "Web Frontend:"
	@echo "  make web-install        - Install npm dependencies"
	@echo "  make web-test           - Run frontend tests (vitest)"
	@echo "  make web-dev            - Start dev server with hot reload"
	@echo "  make web-build          - Build frontend bundle"
	@echo "  make web-restore-placeholder - Restore tracked web/dist placeholder files"
	@echo "  make web-preview        - Preview production build"
	@echo "  make web-clean          - Clean node_modules and build artifacts"
	@echo ""
	@echo "Cleanup:"
	@echo "  make clean              - Remove build artifacts and coverage"
	@echo "  make clean-all          - Deep clean (includes test binaries)"
	@echo ""
	@echo "Cross-Platform Builds:"
	@echo "  make build-cli-linux    - Build for Linux (amd64)"
	@echo "  make build-cli-darwin   - Build for macOS (universal binary)"
	@echo "  make build-cli-windows  - Build for Windows (amd64)"
	@echo "  make build-cli-all      - Build for all platforms"
	@echo ""
	@echo "Desktop App (clickable .app/.exe):"
	@echo "  make build-app-darwin   - Build macOS .app bundle (universal)"
	@echo "  make build-app-local    - Build macOS .app for host arch (fast, no lipo)"
	@echo "  make build-app-windows  - Build Windows .exe"
	@echo "  make build-app-linux    - Build Linux AppImage (host arch; needs webkit2gtk-4.1 + gtk3)"
	@echo "  make build-app-all      - Build desktop app for all platforms"
	@echo ""
	@echo "Docker:"
	@echo "  make docker-help        - Show Docker-specific commands"
	@echo ""
	@echo "GitHub Actions (act):"
	@echo "  make act-help           - Show act-specific commands"
	@echo ""
	@echo "Variables:"
	@echo "  VERSION=$(VERSION)"
	@echo "  COMMIT=$(COMMIT)"
	@echo ""

# Version information (can be overridden)
VERSION ?= $(shell ./scripts/version.sh)
COMMIT := $(shell git rev-parse --short=12 HEAD 2>/dev/null || echo "unknown")
BUILD_DATE := $(shell date -u '+%Y-%m-%dT%H:%M:%SZ')

# Build flags
LDFLAGS := -ldflags "\
	-X github.com/javinizer/javinizer-go/internal/version.Version=$(VERSION) \
	-X github.com/javinizer/javinizer-go/internal/version.Commit=$(COMMIT) \
	-X github.com/javinizer/javinizer-go/internal/version.BuildDate=$(BUILD_DATE)"

# Optimized build flags (strip debug symbols)
LDFLAGS_RELEASE := -ldflags "\
	-w -s \
	-X github.com/javinizer/javinizer-go/internal/version.Version=$(VERSION) \
	-X github.com/javinizer/javinizer-go/internal/version.Commit=$(COMMIT) \
	-X github.com/javinizer/javinizer-go/internal/version.BuildDate=$(BUILD_DATE)"

# Build the application (single binary with embedded web UI and version info)
build: web-build
	@echo "Building javinizer $(VERSION) (commit: $(COMMIT))..."
	./scripts/with_embedded_web.sh go build $(LDFLAGS) -o bin/javinizer ./cmd/javinizer

# Run the CLI (primary target)
run:
	go run ./cmd/javinizer

# Run the web/API server using subcommand
run-api:
	go run ./cmd/javinizer web

# Run API with hot reload (requires air, falls back to go run air)
run-api-dev:
	@if command -v air >/dev/null 2>&1; then \
		air -c .air.toml; \
	else \
		echo "air not found in PATH, running via go run github.com/air-verse/air@latest"; \
		go run github.com/air-verse/air@latest -c .air.toml; \
	fi

# Run tests
test:
	go test -v ./...

# Per-package timeout for short tests (can be overridden)
ifeq ($(OS),Windows_NT)
TEST_SHORT_TIMEOUT ?= 300s
else
TEST_SHORT_TIMEOUT ?= 120s
endif

# Run short/fast tests (for pre-commit hooks)
test-short:
	go test -short -timeout=$(TEST_SHORT_TIMEOUT) ./...

# Run tests with race detector (critical for concurrent code)
test-race:
	@echo "Running race detector on concurrent packages..."
	go test -race -v ./internal/worker/...
	go test -race -v ./internal/downloader/...
	go test -race -v ./internal/tui/...
	go test -race -v ./internal/websocket/...
	go test -race -v ./internal/api/...

# Run tests with verbose output
test-verbose:
	go test -v -count=1 ./...

# Run benchmarks
bench:
	go test -bench=. -benchmem ./...

# Generate coverage report using Go's built-in coverage
# Uses -coverpkg to aggregate coverage across all packages in a single run
# Excludes: mocks (generated), tui (interactive UI), docs (generated API docs), testutil (test helpers), coveragecheck (coverage utility), coverage (coverage utility), javinizer-e2e (E2E test binary — boots a mock backend for Playwright; exercised by the fullstack-e2e suite, not unit tests), web (frontend)
# Output: coverage.out (compatible with go tool cover and Codecov)
# Shows: per-package test output, then displays clean line coverage summary
coverage:
	@echo "Running tests and generating coverage report..."
	@rm -f coverage.out
	@pkgs=$$(go list ./... | grep -Ev '(/cmd/(coveragecheck|javinizer-e2e)$$)|(/internal/coverage$$)|(/(mocks|tui|docs|testutil|web)(/|$$))'); \
	tmp=$$(mktemp); \
	go test -covermode=atomic -coverprofile=coverage.out -coverpkg=$$(echo $$pkgs | tr ' ' ',') -count=1 $$pkgs >$$tmp 2>&1; status=$$?; \
	awk '/^(ok|github|--- FAIL|FAIL|panic)/ { print; fflush() }' $$tmp; \
	if [ $$status -ne 0 ]; then \
		echo ""; \
		echo "--- full test output (run failed; the filtered summary above hides assertion details) ---" >&2; \
		cat $$tmp; \
		rm -f $$tmp; \
		echo ""; \
		echo "✗ Coverage test run failed (exit $$status) — coverage.out is PARTIAL and would report phantom misses" >&2; \
		exit $$status; \
	fi; \
	rm -f $$tmp
	@echo ""
	@go run ./cmd/coveragecheck --metric line --profile coverage.out 2>/dev/null

# Generate HTML coverage report from coverage.out
coverage-html: coverage
	@go tool cover -html=coverage.out -o coverage.html
	@echo "Coverage HTML report: coverage.html"
	@echo "Open in browser: open coverage.html"

# Display per-package coverage breakdown
coverage-pkg: coverage
	@echo "Per-package coverage breakdown:"
	@./scripts/coverage_by_pkg.sh

# Check if coverage meets minimum threshold using Codecov-compatible line coverage.
coverage-check: coverage
	@./scripts/check_coverage.sh 75 coverage.out

# Show patch coverage (new/changed lines vs main) — mirrors codecov/patch.
# Reads codecov.yml's ignore list and go.mod's module path so the local result
# matches what codecov reports on the PR. Requires a clean working tree with
# the base ref (main) available locally (git fetch upstream main first if not).
coverage-patch: coverage
	@go run ./cmd/coveragecheck --patch --profile coverage.out --base main

# Enforce 80%% patch coverage (matching codecov.yml's patch target). Fails the
# build if any new/changed lines are uncovered. Use before pushing to catch
# codecov/patch failures locally instead of after a CI round-trip.
coverage-patch-check: coverage
	@go run ./cmd/coveragecheck --patch --profile coverage.out --base main --min 80

# Strict patch gate — 100%% of changed lines must be covered. This mirrors the
# PR-completion bar (resolve-pr workflow); codecov.yml's 80%% remains the CI floor.
coverage-patch-strict: coverage
	@go run ./cmd/coveragecheck --patch --profile coverage.out --base main --min 100

# Test coverage goal - enforces 90% line coverage
test-coverage: coverage
	@./scripts/check_coverage.sh 90 coverage.out

# Validate config synchronization (defaults.go matches config.yaml.example, no hardcoded scraper values)
config-drift:
	@./scripts/validate-config-sync.sh

# Sync configs/config.yaml.example from the embedded master copy
# (internal/config/config.yaml.example). The embedded copy is the source of
# truth — it is what the binary reads at runtime via //go:embed. Run this
# after editing the embedded copy, then commit both files together.
config-sync:
	@echo "Syncing configs/config.yaml.example from internal/config/config.yaml.example..."
	@cp internal/config/config.yaml.example configs/config.yaml.example
	@echo "✓ Synced. Review and commit: git add configs/config.yaml.example internal/config/config.yaml.example"

# Check that internal/models does not import internal/config (enforces dependency direction)
check-import-guard:
	@./scripts/check_import_guard.sh

# Run i18n catalog validation (missing keys, placeholder mismatch, coverage)
i18n-check:
	@bash scripts/i18n-check.sh

# Check for hard-coded http.Client{Timeout: ...} literals outside the allowlist.
# Prevents the #152 bug class (config timeout shadowed by a hard-coded literal).
check-no-hardcoded-timeouts:
	@./scripts/check_no_hardcoded_timeouts.sh

# Run full CI test suite
ci: vet lint vuln coverage-check test-race config-drift check-import-guard check-mocks i18n-check check-no-hardcoded-timeouts
	@echo "All CI checks passed!"

# Run full CI suite including frontend tests
ci-full: ci web-test
	@echo "All CI checks (including frontend) passed!"

# Simulate GitHub Actions CI locally (with pretty output)
simulate-ci:
	@./scripts/simulate-ci.sh

# Clean build artifacts and coverage reports
clean:
	@echo "Cleaning build artifacts and coverage reports..."
	rm -rf bin/
	rm -f coverage.out coverage.html
	@echo "Clean complete!"

# Deep clean - removes everything including test binaries and caches
clean-all: clean
	@echo "Deep cleaning (test binaries, caches, etc.)..."
	rm -f *.test cli javinizer api.test
	rm -f COMMIT_MESSAGE.txt zen_*.changeset
	go clean -cache -testcache -modcache
	@echo "Deep clean complete!"

# Download dependencies (includes dev tools via tools.go)
deps:
	go mod download
	go mod tidy

# Install the binary
install: web-build
	./scripts/with_embedded_web.sh go build $(LDFLAGS) -o $(GOPATH)/bin/javinizer ./cmd/javinizer

# Format code
fmt:
	go fmt ./...

# Run go vet
vet:
	go vet ./...

# Run linter
lint:
	golangci-lint run

vuln:
	@echo "Running govulncheck..."
	@go run golang.org/x/vuln/cmd/govulncheck@latest ./...

# Generate Swagger API documentation
swagger:
	@echo "Generating Swagger documentation..."
	@export PATH=$$PATH:$$(go env GOPATH)/bin && swag init -g cmd/javinizer/commands/api/command.go -o docs/swagger --parseDependency --parseInternal
	@echo "✅ Swagger documentation generated"
	@echo "   - docs/swagger/swagger.json ($(shell wc -l < docs/swagger/swagger.json) lines)"
	@echo "   - docs/swagger/swagger.yaml ($(shell wc -l < docs/swagger/swagger.yaml) lines)"
	@echo "   - docs/swagger/docs.go ($(shell wc -l < docs/swagger/docs.go) lines)"

# Check if swagger documentation is up to date
check-swagger:
	@echo "Checking Swagger documentation..."
	@export PATH=$$PATH:$$(go env GOPATH)/bin && swag init -g cmd/javinizer/commands/api/command.go -o docs/swagger --parseDependency --parseInternal
	@if ! git diff --quiet -- docs/swagger/; then \
		echo "❌ Swagger documentation is out of date"; \
		echo "Run 'make swagger' and commit the changes"; \
		exit 1; \
	fi
	@echo "✅ Swagger documentation is up to date"

# Alias for backward compatibility
docs: swagger
	@echo "✅ Documentation up-to-date"
	@echo "View at: http://localhost:8765/docs"

# Generate mocks from interfaces using mockery v3
# Built from tools/mockery (mockery v3.8.0 + x/tools v0.52.0) because the
# x/tools v0.49.0 pinned in mockery v3.8.0 cannot read Go 1.27 export data
# (format v5), breaking package loading on this module.
# Config: .mockery.yaml
# Output: internal/mocks/
MOCKERY_BIN := bin/mockery

$(MOCKERY_BIN): tools/mockery/go.mod tools/mockery/go.sum
	@cd tools/mockery && go build -o ../../$(MOCKERY_BIN) github.com/vektra/mockery/v3

mocks: $(MOCKERY_BIN)
	@echo "Generating mocks with mockery..."
	@$(MOCKERY_BIN) --config .mockery.yaml
	@echo "Post-processing: Unifying package names to 'mocks'..."
	@for file in internal/mocks/*.go; do \
		sed -i.bak 's/^package models$$/package mocks/' "$$file"; \
		sed -i.bak 's/^package database$$/package mocks/' "$$file"; \
		sed -i.bak 's/^package httpclient$$/package mocks/' "$$file"; \
		sed -i.bak 's/^package aggregator$$/package mocks/' "$$file"; \
		rm -f "$$file.bak"; \
	done
	@echo "Mock generation complete! Generated mocks in internal/mocks/"

# Check if mocks are up to date (mirrors check-swagger)
check-mocks: $(MOCKERY_BIN)
	@echo "Checking mocks are up to date..."
	@$(MOCKERY_BIN) --config .mockery.yaml >/dev/null
	@for file in internal/mocks/*.go; do \
		sed -i.bak 's/^package models$$/package mocks/' "$$file"; \
		sed -i.bak 's/^package database$$/package mocks/' "$$file"; \
		sed -i.bak 's/^package httpclient$$/package mocks/' "$$file"; \
		sed -i.bak 's/^package aggregator$$/package mocks/' "$$file"; \
		rm -f "$$file.bak"; \
	done
	@if ! git diff --quiet -- internal/mocks/; then \
		echo "❌ Mocks are out of date"; \
		echo "Run 'make mocks' and commit the changes"; \
		exit 1; \
	fi
	@echo "✅ Mocks are up to date"

# Web frontend targets
web-dev:
	cd web/frontend && npm run dev -- --port 5174

web-build:
	@if [ ! -d web/frontend/node_modules ]; then \
		echo "web/frontend/node_modules missing — running npm ci..."; \
		cd web/frontend && npm ci; \
	fi
	cd web/frontend && npm run build

web-restore-placeholder:
	rm -rf web/dist
	git checkout -- web/dist/

web-preview:
	cd web/frontend && npm run preview

web-install:
	cd web/frontend && npm install

web-test:
	cd web/frontend && npm run test

# Full-stack E2E test suite — real browser → real SvelteKit frontend → real HTTP
# transport → real Go API server (cmd/javinizer-e2e with the `e2emock`
# scraper substituted at the scraper seam) → real worker pipeline → real
# :memory: SQLite → real e2emock scraper. NO page.route / API mocks.
#
# Playwright's webServer config spawns both the Go backend (on port
# 18080) and the Vite dev server (on port 5175) automatically + kills
# both on teardown. Spec layout lives under web/frontend/tests/fullstack/
# — see playwright.fullstack.config.ts header for the directory map.
test-e2e-fullstack:
	cd web/frontend && npx playwright test --config=playwright.fullstack.config.ts

# Isolated review-load timeout E2E — uses a dedicated frontend port and
# VITE_REVIEW_DETAIL_TIMEOUT_MS=50 so it cannot contaminate the default suite.
test-e2e-review-load:
	cd web/frontend && npx playwright test --config=playwright.review-load.config.ts

# Frontend-only E2E suite — mocked API (page.route), NO backend. Spawns
# only the Vite dev server (port 5176). Fast + deterministic: pins pure
# frontend rendering + interaction logic + env-gated UI states (e.g.
# install_environment=desktop) without a real Go server. Specs MUST mock
# every /api endpoint they touch (including /api/v1/auth/status). Spec
# layout lives under web/frontend/tests/frontend/ — see
# playwright.frontend.config.ts header for the 4-suite distinction map.
test-e2e-frontend:
	cd web/frontend && npx playwright test --config=playwright.frontend.config.ts

# Static-flavor E2E suite — the SHIPPED deployment flavor: production
# adapter-static build staged into web/dist (scripts/with_embedded_web.sh),
# compiled into the real e2e binary (//go:embed all:dist), served by Gin's
# NoRoute fallback exactly as Docker / release binaries serve it. Asserts
# hard page loads render route content with zero __data.json server-data
# requests — the regression class both dev-server suites mask. Specs live
# under web/frontend/tests/static/ — see playwright.static.config.ts header.
test-static-e2e: web-build
	./scripts/with_embedded_web.sh go build -o bin/javinizer-e2e-static ./cmd/javinizer-e2e
	cd web/frontend && npx playwright test --config=playwright.static.config.ts

# Backward-compat alias — the field-drop regression suite runs as part
# of the general fullstack suite now. Redirects to the new target.
test-e2e-field-drop: test-e2e-fullstack

# CLI E2E suite — builds the real `javinizer` binary and drives its
# subcommands (sort, version, --help, --dry-run) end-to-end through os/exec.
# Hermetic + deterministic: JAVINIZER_E2E_SCRAPERS=true substitutes the
# offline e2emock scraper at the dependency-injection seam, so no real
# network scraping happens. TestMain builds the binary once; tests run with
# the `e2e` build tag so this stays out of `make test` / `make test-short`.
test-e2e-cli:
	go test -tags e2e -timeout 300s ./test/e2e/cli/

# ⚠️  LOCAL ONLY — do NOT run in CI. Real-network scraper tests that hit
# live JAV sites (r18dev, dmm, javlibrary, ...) over the public internet.
# Requires the developer's real config (proxy/FlareSolverr/browser) and
# the JAVINIZER_LIVE_E2E=true env var. The `live` build tag + env guard
# keep this out of `make test` / `make test-short` / CI entirely.
# Future: lift this onto a scheduled server to track scraping degradation.
# Run a single scraper: -run 'TestLive_Scrapers/r18dev'
# JSON output (for tracking): JAVINIZER_LIVE_E2E_JSON=true
test-e2e-live:
	@echo "⚠️  Running LIVE scraper tests against real sites (local only)"
	JAVINIZER_LIVE_E2E=true go test -tags live -timeout 1800s ./test/e2e/live/

web-clean:
	rm -rf web/frontend/node_modules web/frontend/.svelte-kit web/frontend/build

# ============================================================================
# CLI Binary Build Targets (for multi-platform releases)
# ============================================================================

build-cli-linux: web-build
	@echo "Building CLI for Linux (amd64) - $(VERSION)..."
	CGO_ENABLED=1 GOOS=linux GOARCH=amd64 ./scripts/with_embedded_web.sh go build $(LDFLAGS_RELEASE) -o bin/javinizer-linux-amd64 ./cmd/javinizer

build-cli-darwin: web-build
	@echo "Building CLI for macOS - $(VERSION)..."
	CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 ./scripts/with_embedded_web.sh go build $(LDFLAGS_RELEASE) -o bin/javinizer-darwin-amd64 ./cmd/javinizer
	CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 ./scripts/with_embedded_web.sh go build $(LDFLAGS_RELEASE) -o bin/javinizer-darwin-arm64 ./cmd/javinizer
	lipo -create bin/javinizer-darwin-amd64 bin/javinizer-darwin-arm64 -output bin/javinizer-darwin-universal

build-cli-windows: web-build
	@echo "Building CLI for Windows - $(VERSION)..."
	CGO_ENABLED=1 GOOS=windows GOARCH=amd64 ./scripts/with_embedded_web.sh go build $(LDFLAGS_RELEASE) -o bin/javinizer-windows-amd64.exe ./cmd/javinizer

build-cli-all: build-cli-linux build-cli-darwin build-cli-windows
	@echo "All CLI binaries built successfully!"
	@ls -lh bin/

# ============================================================================
# Desktop App Build Targets (clickable .app / .exe via Wails)
# ============================================================================
#
# These produce a single clickable application that opens a native desktop
# window over the embedded API server + Web UI. CLI and TUI subcommands are
# preserved inside the same binary.
#
# Build tags:
#   desktop    - compiles internal/desktop/app.go (the Wails runner); without
#                it, app_stub.go provides a no-op so normal CLI/test builds
#                never pull in the Wails dependency or its webview headers.
#   production - Wails requires this (or `dev`) to enable its real frontend;
#                the default build returns "will not build without the correct
#                build tags".
#
# -X internal/desktop.BuildDesktop=1 makes a no-arg launch open the GUI instead
# of printing help (CLI release builds keep BuildDesktop=0 → help on no-args).

DESKTOP_TAGS := desktop,production
DESKTOP_LDFLAGS := -ldflags "\
	-w -s \
	-X github.com/javinizer/javinizer-go/internal/version.Version=$(VERSION) \
	-X github.com/javinizer/javinizer-go/internal/version.Commit=$(COMMIT) \
	-X github.com/javinizer/javinizer-go/internal/version.BuildDate=$(BUILD_DATE) \
	-X github.com/javinizer/javinizer-go/internal/desktop.BuildDesktop=1"

# Windows desktop builds need -H windowsgui (the GUI subsystem flag) so the
# .exe doesn't open a console window on launch. The Wails CLI adds this
# automatically, but these targets build via `go build` directly.
DESKTOP_LDFLAGS_WINDOWS := -ldflags "\
	-w -s -H windowsgui \
	-X github.com/javinizer/javinizer-go/internal/version.Version=$(VERSION) \
	-X github.com/javinizer/javinizer-go/internal/version.Commit=$(COMMIT) \
	-X github.com/javinizer/javinizer-go/internal/version.BuildDate=$(BUILD_DATE) \
	-X github.com/javinizer/javinizer-go/internal/desktop.BuildDesktop=1"

build-app-darwin: web-build
	@echo "Building desktop app for macOS - $(VERSION)..."
	CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 CGO_LDFLAGS="-framework UniformTypeIdentifiers" \
		./scripts/with_embedded_web.sh go build -tags '$(DESKTOP_TAGS)' $(DESKTOP_LDFLAGS) \
		-o bin/javinizer-app-darwin-amd64 ./cmd/javinizer
	CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 CGO_LDFLAGS="-framework UniformTypeIdentifiers" \
		./scripts/with_embedded_web.sh go build -tags '$(DESKTOP_TAGS)' $(DESKTOP_LDFLAGS) \
		-o bin/javinizer-app-darwin-arm64 ./cmd/javinizer
	lipo -create bin/javinizer-app-darwin-amd64 bin/javinizer-app-darwin-arm64 \
		-output bin/javinizer-app-darwin-universal
	rm -f bin/javinizer-app-darwin-amd64 bin/javinizer-app-darwin-arm64
	./scripts/package-app-darwin.sh bin/javinizer-app-darwin-universal \
		bin/Javinizer.app $(VERSION) internal/desktop/icons/javinizer.icns
	@echo "macOS desktop app built: bin/Javinizer.app"

# build-app-local: single host-arch desktop build (no lipo). Faster for
# local iteration — skips the amd64 cross-compile that build-app-darwin
# performs. Produces bin/Javinizer.app for the current architecture.
build-app-local: web-build
	@echo "Building desktop app for macOS (host arch) - $(VERSION)..."
	CGO_ENABLED=1 GOOS=darwin GOARCH=$$(go env GOHOSTARCH) CGO_LDFLAGS="-framework UniformTypeIdentifiers" \
		./scripts/with_embedded_web.sh go build -tags '$(DESKTOP_TAGS)' $(DESKTOP_LDFLAGS) \
		-o bin/javinizer-app-darwin-local ./cmd/javinizer
	./scripts/package-app-darwin.sh bin/javinizer-app-darwin-local \
		bin/Javinizer.app $(VERSION) internal/desktop/icons/javinizer.icns
	@echo "macOS desktop app built (host arch): bin/Javinizer.app"

# NOTE: Wails v2 Windows builds require CGO (go-webview2). The release workflow
# runs natively on windows-latest. This target also supports cross-compiling
# from macOS/Linux when mingw-w64 is installed.
#
# When cross-compiling, Go must use the mingw C compiler; otherwise it falls
# back to the host's clang/gcc, which targets the host OS/arch and fails with
# "unsupported option '-mthreads'" / "gcc_amd64.S: unknown token". We detect
# mingw on PATH and export CC for the build. On a native Windows runner no
# mingw is present, so CC is left unset and the native cl/gcc is used.
#
# The icon is embedded via a .syso COFF resource generated by the genicon tool
# (internal/desktop/icons/genicon) from internal/desktop/icons/javinizer.ico.
# `go build` links .syso files found in the main package dir (cmd/javinizer)
# automatically. The release workflow
# (.github/workflows/cli-release.yml build-desktop-windows) uses the same tool.
#
# genicon embeds the icon as RT_GROUP_ICON at resource ID 3 — the ID Wails'
# window manager loads (wails v2 .../windows/winc/app.go: AppIconID = 3). The
# rsrc -ico CLI assigns ID 1, which renders the .exe/taskbar icon but leaves
# the in-app window title-bar icon generic.
WINDOWS_ICON_ICO := internal/desktop/icons/javinizer.ico
WINDOWS_ICON_SYSO := cmd/javinizer/javinizer.syso
MINGW_CC := $(shell command -v x86_64-w64-mingw32-gcc 2>/dev/null)

build-app-windows: web-build $(WINDOWS_ICON_SYSO)
	@echo "Building desktop app for Windows - $(VERSION)..."
	$(if $(MINGW_CC),CC=$(MINGW_CC)) CGO_ENABLED=1 GOOS=windows GOARCH=amd64 \
		./scripts/with_embedded_web.sh go build -tags '$(DESKTOP_TAGS)' $(DESKTOP_LDFLAGS_WINDOWS) \
		-o bin/Javinizer.exe ./cmd/javinizer
	@echo "Windows desktop app built: bin/Javinizer.exe"

# Generate the Windows icon resource (.syso) from the .ico via genicon (pure
# Go, runs anywhere). Embeds RT_GROUP_ICON at ID 3 so both the executable
# icon and the Wails window title-bar icon resolve. Skipped when the .syso is
# newer than the .ico (stamp file semantics).
$(WINDOWS_ICON_SYSO): $(WINDOWS_ICON_ICO) internal/desktop/icons/genicon/main.go
	@echo "Embedding Windows icon -> $(WINDOWS_ICON_SYSO)"
	go run ./internal/desktop/icons/genicon -ico $(WINDOWS_ICON_ICO) -o $(WINDOWS_ICON_SYSO) -arch amd64

# Linux desktop build: produces an AppImage that bundles libwebkit2gtk + gtk3
# so it runs on any distro without preinstalling webkit. Build deps (Ubuntu):
#   sudo apt-get install -y libwebkit2gtk-4.1-dev libgtk-3-dev
# AppImage packaging (scripts/package-app-linux.sh) downloads linuxdeploy +
# appimagetool at runtime and must run on Linux. The webkit2_41 tag selects
# libwebkit2gtk-4.1 (Ubuntu 24.04's version).
build-app-linux: web-build
	@echo "Building desktop app for Linux - $(VERSION)..."
	CGO_ENABLED=1 GOOS=linux GOARCH=$$(go env GOARCH) \
		./scripts/with_embedded_web.sh go build -tags '$(DESKTOP_TAGS),webkit2_41' $(DESKTOP_LDFLAGS) \
		-o bin/javinizer-app-linux-$$(go env GOARCH) ./cmd/javinizer
	./scripts/package-app-linux.sh bin/javinizer-app-linux-$$(go env GOARCH) \
		bin/Javinizer-linux-$$(uname -m).AppImage $(VERSION)
	@echo "Linux desktop app built: bin/Javinizer-linux-$$(uname -m).AppImage"

build-app-all: build-app-darwin build-app-windows
	@echo "All cross-platform desktop apps built successfully!"
	@echo "(Linux AppImage must be built on Linux: make build-app-linux)"
	@ls -lh bin/Javinizer.app bin/Javinizer.exe

# ============================================================================
# Local GitHub Actions Testing with act
# ============================================================================

act-list:
	@echo "Available workflows:"
	@act -l

act-test:
	@echo "Running test workflow locally..."
	@act -j test

act-build:
	@echo "Running build workflow locally..."
	@act -j build

act-lint:
	@echo "Running lint workflow locally..."
	@act -j lint

act-docker:
	@echo "Running Docker build workflow locally..."
	@act -W .github/workflows/test.yml -j docker-build

act-cli-release:
	@echo "Testing CLI release workflow locally..."
	@act -W .github/workflows/cli-release.yml --env GITHUB_REF=refs/tags/v1.0.0-test

act-ci:
	@echo "Testing all CI workflows locally with act..."
	@act -j test -j lint -j build -j coverage

act-dry:
	@echo "Dry run - show what would execute:"
	@act -n

act-help:
	@echo "act - Local GitHub Actions Testing"
	@echo ""
	@echo "Available targets:"
	@echo "  make act-list          - List all workflows"
	@echo "  make act-test          - Run test job"
	@echo "  make act-build         - Run build job"
	@echo "  make act-lint          - Run lint job"
	@echo "  make act-docker        - Test Docker workflow"
	@echo "  make act-cli-release   - Test CLI release workflow"
	@echo "  make act-ci            - Run all CI jobs"
	@echo "  make act-dry           - Dry run (show execution plan)"
	@echo ""
	@echo "Direct act commands:"
	@echo "  act -l                 - List workflows"
	@echo "  act -j <job>           - Run specific job"
	@echo "  act -v                 - Verbose output"
	@echo "  act -n                 - Dry run"

# ============================================================================
# Docker Build and Deployment Targets
# ============================================================================

# Docker image configuration
DOCKER_IMAGE := javinizer
DOCKER_TAG := $(VERSION)
DOCKER_REGISTRY ?= ghcr.io/javinizer
DOCKER_FULL_IMAGE := $(DOCKER_IMAGE):$(DOCKER_TAG)
DOCKER_CONTAINER_NAME := javinizer

# Build Docker image with version injection
docker-build:
	@echo "Building Docker image $(DOCKER_FULL_IMAGE)..."
	docker build \
		--build-arg VERSION=$(VERSION) \
		--build-arg COMMIT=$(COMMIT) \
		--build-arg BUILD_DATE=$(BUILD_DATE) \
		-t $(DOCKER_FULL_IMAGE) \
		-t $(DOCKER_IMAGE):latest \
		.
	@echo "Docker image built successfully!"
	@docker images $(DOCKER_IMAGE)

# Build Docker image without cache (force clean build)
docker-build-no-cache:
	@echo "Building Docker image $(DOCKER_FULL_IMAGE) without cache..."
	docker build --no-cache \
		--build-arg VERSION=$(VERSION) \
		--build-arg COMMIT=$(COMMIT) \
		--build-arg BUILD_DATE=$(BUILD_DATE) \
		-t $(DOCKER_FULL_IMAGE) \
		-t $(DOCKER_IMAGE):latest \
		.
	@echo "Docker image built successfully!"
	@docker images $(DOCKER_IMAGE)

# Run Docker container (detached mode).
# Mirrors docker-compose.yml's canonical env (host port, trusted CIDRs for
# /auth/setup from the Docker bridge gateway, data + media mounts). Override via:
#   make docker-run HOST_PORT=9090 MEDIA_DIR=./test-videos
# The setup CIDR is needed because the container sees the host as the Docker
# bridge gateway (172.x), not 127.0.0.1, so the /auth/setup localhost guard
# would reject the first-run setup screen without it.
# Port on the host to publish the container's 8765. 8765 is distinctive
# (8080 is too common and collides with many dev tools).
# Override: make docker-run HOST_PORT=9090
# Matches docker-compose.yml's HOST_PORT env var so `make` and `compose` agree.
HOST_PORT ?= 8765
# Host directory mounted at /media (input files, writable so organize works).
# Override: make docker-run MEDIA_DIR=./test-videos
MEDIA_DIR ?= $(PWD)/media
# Host directory for persistent app state (db, config, logs, temp). Defaults
# to ./data, matching docker-compose.yml's ./data:/javinizer volume convention.
# Already covered by .gitignore's /data/ entry. Does NOT collide with the
# `javinizer` binary (the binary is a file at the repo root; data/ is a dir).
# Override: make docker-run DATA_DIR=./my-data
DATA_DIR ?= $(PWD)/data

# Run Docker container (detached mode)
docker-run:
	@echo "Starting Docker container $(DOCKER_CONTAINER_NAME) on port $(HOST_PORT)..."
	-docker stop $(DOCKER_CONTAINER_NAME) 2>/dev/null || true
	-docker rm $(DOCKER_CONTAINER_NAME) 2>/dev/null || true
	@mkdir -p $(DATA_DIR)
	$(eval ABS_MEDIA_DIR := $(abspath $(MEDIA_DIR)))
	@if [ ! -d "$(ABS_MEDIA_DIR)" ]; then echo "warning: MEDIA_DIR '$(MEDIA_DIR)' does not exist or is not a directory — container will start but /media will be empty"; fi
	docker run -d \
		--name $(DOCKER_CONTAINER_NAME) \
		-p "$(HOST_PORT):8765" \
		-e JAVINIZER_SETUP_TRUSTED_CIDRS=172.16.0.0/12 \
		-v "$(DATA_DIR):/javinizer" \
		-v "$(ABS_MEDIA_DIR):/media" \
		$(DOCKER_FULL_IMAGE)
	@echo "Container started! Access at http://localhost:$(HOST_PORT)"
	@echo "Logs: docker logs -f $(DOCKER_CONTAINER_NAME)"

# Stop and remove Docker container
docker-stop:
	@echo "Stopping Docker container $(DOCKER_CONTAINER_NAME)..."
	-docker stop $(DOCKER_CONTAINER_NAME)
	-docker rm $(DOCKER_CONTAINER_NAME)
	@echo "Container stopped and removed"

# Restart Docker container (stop + run)
docker-restart: docker-stop docker-run

# View container logs
docker-logs:
	docker logs -f $(DOCKER_CONTAINER_NAME)

# Validate Docker image metadata and startup
docker-test: docker-build
	@echo "Validating Docker image version output..."
	docker run --rm --entrypoint /usr/local/bin/javinizer $(DOCKER_FULL_IMAGE) version --short

# Clean Docker images and containers
docker-clean:
	@echo "Cleaning Docker images and containers..."
	-docker stop $(DOCKER_CONTAINER_NAME) 2>/dev/null || true
	-docker rm $(DOCKER_CONTAINER_NAME) 2>/dev/null || true
	-docker rmi $(DOCKER_IMAGE):latest 2>/dev/null || true
	-docker rmi $(DOCKER_FULL_IMAGE) 2>/dev/null || true
	@echo "Docker cleanup complete"

# Push Docker image to registry (requires login)
docker-push:
	@echo "Pushing Docker image to $(DOCKER_REGISTRY)..."
	docker tag $(DOCKER_FULL_IMAGE) $(DOCKER_REGISTRY)/$(DOCKER_FULL_IMAGE)
	docker tag $(DOCKER_IMAGE):latest $(DOCKER_REGISTRY)/$(DOCKER_IMAGE):latest
	docker push $(DOCKER_REGISTRY)/$(DOCKER_FULL_IMAGE)
	docker push $(DOCKER_REGISTRY)/$(DOCKER_IMAGE):latest
	@echo "Docker image pushed successfully!"

# ============================================================================
# Docker Compose Targets
# ============================================================================

# Start services with docker-compose
docker-compose-up:
	@echo "Starting services with docker-compose..."
	docker-compose up -d
	@echo "Services started! Access at http://localhost:$(HOST_PORT)"

# Stop services
docker-compose-down:
	@echo "Stopping docker-compose services..."
	docker-compose down

# Restart services
docker-compose-restart:
	@echo "Restarting docker-compose services..."
	docker-compose restart

# View docker-compose logs
docker-compose-logs:
	docker-compose logs -f

# Build docker-compose services
docker-compose-build:
	@echo "Building docker-compose services..."
	docker-compose build

# Help target for Docker commands
docker-help:
	@echo "Docker Build Targets:"
	@echo ""
	@echo "Build:"
	@echo "  make docker-build              - Build Docker image with version injection"
	@echo "  make docker-build-no-cache     - Build without cache (clean build)"
	@echo ""
	@echo "Run:"
	@echo "  make docker-run                - Run container (detached, host port 8765)"
	@echo "  make docker-run HOST_PORT=9090 MEDIA_DIR=./test-videos  - Custom port + media mount"
	@echo "  make docker-stop               - Stop and remove container"
	@echo "  make docker-logs               - View container logs"
	@echo "  make docker-test               - Validate Docker image version metadata"
	@echo ""
	@echo "Cleanup:"
	@echo "  make docker-clean              - Remove images and containers"
	@echo ""
	@echo "Registry:"
	@echo "  make docker-push               - Push image to registry"
	@echo ""
	@echo "Docker Compose:"
	@echo "  make docker-compose-up         - Start services"
	@echo "  make docker-compose-down       - Stop services"
	@echo "  make docker-compose-restart    - Restart services"
	@echo "  make docker-compose-logs       - View logs"
	@echo "  make docker-compose-build      - Build services"
	@echo ""
	@echo "Variables (can override):"
	@echo "  VERSION=$(VERSION)"
	@echo "  DOCKER_REGISTRY=$(DOCKER_REGISTRY)"
	@echo ""
	@echo "Examples:"
	@echo "  make docker-build VERSION=1.0.0"
	@echo "  make docker-push DOCKER_REGISTRY=ghcr.io/myorg"

.PHONY: test-e2e-offline-sync
test-e2e-offline-sync:
	go build -o bin/javinizer-sync-e2e ./cmd/javinizer-sync-e2e
	cd web/frontend && npx playwright test --config=playwright.sync.config.ts