Makefile
Rama main · réplica de lectura del repositorio original.
.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