.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 - 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