Repository navigation
Expand file tree
/
Copy pathMakefile
More file actions
368 lines (322 loc) · 15.5 KB
/
Copy pathMakefile
File metadata and controls
368 lines (322 loc) · 15.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
# qBittorrent WebUI Makefile
# Load .env file if it exists (silently)
ifneq (,$(wildcard .env))
include .env
export
endif
# Windows compatibility: run recipes through Git Bash so POSIX tools work
ifeq ($(OS),Windows_NT)
GIT_BASH ?= C:/Progra~1/Git/bin/bash.exe
override SHELL := $(GIT_BASH)
override MAKESHELL := $(SHELL)
.SHELLFLAGS := -lc
endif
# Variables
VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
GIT_COMMIT := $(shell git rev-parse HEAD 2> /dev/null)
GIT_TAG := $(shell git describe --abbrev=0 --tags)
BUILD_DATE := $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
BINARY_NAME = qui$(if $(filter Windows_NT,$(OS)),.exe)
BUILD_DIR = build
WEB_DIR = web
INTERNAL_WEB_DIR = internal/web
# Go build flags
LDFLAGS = -ldflags "-X github.com/autobrr/qui/internal/buildinfo.Version=$(VERSION) -X github.com/autobrr/qui/internal/buildinfo.Commit=$(GIT_COMMIT) -X github.com/autobrr/qui/internal/buildinfo.Date=$(BUILD_DATE)"
.PHONY: all build frontend backend dev dev-backend dev-frontend dev-expose clean test test-postgres test-frontend help themes-fetch themes-clean lint lint-full lint-ratchet typecheck-frontend lint-json lint-fix fmt gofix-changed gofix-check-changed check-mojibake generate precommit deps docs-dev docs-build mutation mutation-go mutation-web
# Default target
all: build
# Build both frontend and backend
build: frontend backend
build/docker:
@echo "Building docker image..."
docker build -t ghcr.io/autobrr/qui:dev -f distrib/docker/Dockerfile . --build-arg GIT_TAG=$(GIT_TAG) --build-arg GIT_COMMIT=$(GIT_COMMIT) --build-arg BUILD_DATE=$(BUILD_DATE) --build-arg VERSION=$(VERSION)
build/dockerx:
docker buildx build -t ghcr.io/autobrr/qui:dev -f distrib/docker/Dockerfile . --build-arg GIT_TAG=$(GIT_TAG) --build-arg GIT_COMMIT=$(GIT_COMMIT) --build-arg BUILD_DATE=$(BUILD_DATE) --build-arg VERSION=$(VERSION) --platform=linux/amd64,linux/arm64 --pull --load
# Fetch the community themes into the licensed folder. The repo is public, so no token.
# Without network, warn and build with the free themes only.
themes-fetch:
@echo "Fetching community themes..."
@rm -rf .themes-temp internal/themes/assets/premium/*.css internal/themes/assets/premium/LICENSE
@if git clone --quiet --depth=1 --branch main --filter=blob:none --sparse \
https://github.com/autobrr/qui-community-themes.git .themes-temp && \
git -C .themes-temp sparse-checkout set --cone themes; then \
cp .themes-temp/themes/[!_]*.css .themes-temp/LICENSE internal/themes/assets/premium/ && \
echo "Community themes fetched"; \
else \
echo "WARNING: could not fetch the community themes, building with the free themes only"; \
fi
@rm -rf .themes-temp
# Clean community themes
themes-clean:
@echo "Cleaning community themes..."
rm -f internal/themes/assets/premium/*.css internal/themes/assets/premium/LICENSE
# Build frontend
frontend:
@echo "Building frontend..."
cd $(WEB_DIR) && pnpm install && pnpm build
@echo "Copying frontend assets..."
rm -rf $(INTERNAL_WEB_DIR)/dist
cp -r $(WEB_DIR)/dist $(INTERNAL_WEB_DIR)/
# Build backend
backend: themes-fetch
@echo "Building backend..."
go build $(LDFLAGS) -o $(BINARY_NAME) ./cmd/qui
# Development mode - run both frontend and backend
dev:
@echo "Starting development mode..."
@make -j 2 dev-backend dev-frontend
# Run backend with hot reload (requires air)
dev-backend:
@echo "Starting backend development server..."
air -c .air.toml
# Run frontend development server
dev-frontend:
@echo "Starting frontend development server..."
cd $(WEB_DIR) && pnpm dev
# Development mode with frontend exposed on 0.0.0.0
dev-expose:
@echo "Starting development mode with frontend exposed on 0.0.0.0..."
@make -j 2 dev-backend dev-frontend-expose
# Run frontend development server exposed on 0.0.0.0
dev-frontend-expose:
@echo "Starting frontend development server (exposed on 0.0.0.0)..."
cd $(WEB_DIR) && pnpm dev --host
# Clean build artifacts
clean: themes-clean
@echo "Cleaning..."
rm -rf $(WEB_DIR)/dist $(INTERNAL_WEB_DIR)/dist $(BINARY_NAME) $(BUILD_DIR)
# Run tests
test:
@echo "Running tests..."
go test -count=1 -v ./...
# Run Postgres integration tests with a temporary server.
test-postgres:
go run ./internal/testutil/postgres
# Run frontend tests (vitest)
test-frontend:
@echo "Running frontend tests..."
cd $(WEB_DIR) && pnpm test
# Validate OpenAPI specification
test-openapi:
@echo "Validating OpenAPI specification..."
go test -count=1 -v ./internal/web/swagger
# Mutation testing of the source that this branch changes against MUTATION_BASE.
# A report, not a gate: some surviving mutants change no behaviour.
MUTATION_BASE ?= origin/develop
MUTATION_DIR ?= $(or $(XDG_CACHE_HOME),$(HOME)/.cache)/qui-mutation
mutation: mutation-go mutation-web
# Each run gets its own worktree of HEAD under MUTATION_DIR, so commit first.
# The worktree goes away when the run ends, also on Ctrl-C.
MUTATION_WORKTREE = mkdir -p "$(abspath $(MUTATION_DIR))" && \
run=$$(mktemp -d "$(abspath $(MUTATION_DIR))/run.XXXXXX") && \
trap 'cd "$(CURDIR)"; git worktree remove --force "$$run/wt" 2>/dev/null; rm -rf "$$run"' EXIT && \
trap 'exit 130' INT TERM && \
git worktree add --quiet --detach "$$run/wt" HEAD
# gremlins copies the whole module once per worker, and the worktree has no
# node_modules or build output. --diff matches files only when gremlins runs
# on the module root, so the coverage run covers every package, and only the
# changed lines are mutated. gremlins reads each diff hunk as one block of
# added lines, so git must write the diff with no context lines.
mutation-go:
@git diff --quiet --diff-filter=d --merge-base $(MUTATION_BASE) HEAD -- '*.go' ':!*_test.go'; \
case $$? in 0) echo "No committed Go source changes against $(MUTATION_BASE)."; exit 0;; 1) ;; *) exit 1;; esac; \
$(MUTATION_WORKTREE) || exit 1; \
mkdir "$$run/tmp"; \
cd "$$run/wt" && GOFLAGS=-count=1 TMPDIR="$$run/tmp" \
GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=diff.context GIT_CONFIG_VALUE_0=0 \
go run github.com/go-gremlins/gremlins/cmd/gremlins@v0.6.0 unleash \
--diff $(MUTATION_BASE) --timeout-coefficient 3 --output-statuses lctk .
# Stryker changes the files in place, because a test imports a Go file from
# outside web/. The worktree keeps those changes out of the main checkout.
mutation-web:
@changed=$$(git diff --name-only --diff-filter=d --merge-base $(MUTATION_BASE) HEAD -- '$(WEB_DIR)/src/*.ts' ':!*.test.ts' ':!*.d.ts') || exit 1; \
files=$$(printf '%s\n' "$$changed" | sed 's|^$(WEB_DIR)/||' | paste -sd, -); \
if [ -z "$$files" ]; then echo "No committed TypeScript source changes against $(MUTATION_BASE)."; exit 0; fi; \
$(MUTATION_WORKTREE) || exit 1; \
cd "$$run/wt/$(WEB_DIR)" && pnpm install --frozen-lockfile --prefer-offline --silent && \
pnpm exec stryker run --mutate "$$files"
# Format changed code only (fast, for iteration)
fmt:
@echo "Formatting changed Go code..."
@gofiles=$$({ git diff --name-only --diff-filter=d; git diff --name-only --cached --diff-filter=d; } | sort -u | grep '\.go$$' || true); \
if [ -n "$$gofiles" ]; then echo "$$gofiles" | xargs gofmt -w; fi
@echo "Formatting changed frontend code..."
@webfiles=$$({ git diff --name-only --diff-filter=d -- '$(WEB_DIR)/'; git diff --name-only --cached --diff-filter=d -- '$(WEB_DIR)/'; } | sort -u | sed 's|^$(WEB_DIR)/||' | grep -E '\.(ts|tsx|js|jsx|mjs)$$' || true); \
if [ -n "$$webfiles" ]; then cd $(WEB_DIR) && echo "$$webfiles" | xargs pnpm eslint --fix; fi
# Apply go fix to changed Go files only
gofix-changed:
@echo "Running go fix on changed Go files..."
@gofiles=$$({ git diff --name-only --diff-filter=d; git diff --name-only --cached --diff-filter=d; } | sort -u | grep '\.go$$' || true); \
if [ -z "$$gofiles" ]; then \
echo "No changed Go files for go fix."; \
exit 0; \
fi; \
gopkgs=$$(printf '%s\n' "$$gofiles" | xargs -n 1 dirname | sort -u); \
printf '%s\n' "$$gopkgs" | while IFS= read -r pkg; do \
[ -n "$$pkg" ] || continue; \
go fix "./$$pkg" || true; \
done; \
tmp=$$(mktemp); \
printf '%s\n' "$$gopkgs" | while IFS= read -r pkg; do \
[ -n "$$pkg" ] || continue; \
go fix -diff "./$$pkg" >> "$$tmp" || true; \
done; \
if [ -s "$$tmp" ]; then \
echo "go fix left pending changes for changed Go files:"; \
cat "$$tmp"; \
rm -f "$$tmp"; \
echo "Re-run 'make gofix-changed'."; \
exit 1; \
fi; \
rm -f "$$tmp"; \
echo "go fix applied."
# Check go fix drift on changed Go files only (for CI/pre-commit)
gofix-check-changed:
@echo "Checking go fix drift on changed Go files..."
@tmp=$$(mktemp); \
gofiles=$$({ git diff --name-only --diff-filter=d; git diff --name-only --cached --diff-filter=d; } | sort -u | grep '\.go$$' || true); \
if [ -z "$$gofiles" ]; then \
rm -f "$$tmp"; \
echo "No changed Go files for go fix check."; \
exit 0; \
fi; \
gopkgs=$$(printf '%s\n' "$$gofiles" | xargs -n 1 dirname | sort -u); \
printf '%s\n' "$$gopkgs" | while IFS= read -r pkg; do \
[ -n "$$pkg" ] || continue; \
go fix -diff "./$$pkg" >> "$$tmp" || true; \
done; \
if [ -s "$$tmp" ]; then \
echo "go fix changes required for changed Go files:"; \
cat "$$tmp"; \
rm -f "$$tmp"; \
echo "Run 'make gofix-changed'."; \
exit 1; \
fi; \
rm -f "$$tmp"; \
echo "go fix check clean."
# Fail on mojibake: UTF-8 text that an editor read as cp1252 and saved again.
# Example: an em dash becomes "—" (PR #3082). Each UTF-8 lead byte becomes a
# lead character, and each continuation byte becomes a character from
# MOJIBAKE_CONT. The pattern requires the full count of continuation characters
# for each lead (1, 2 or 3), so real text such as Czech "íž" does not match.
# It is an alternation, so it matches the same bytes in every locale.
# The last seven entries of MOJIBAKE_CONT are invisible: a non-breaking space
# (U+00A0), a soft hyphen (U+00AD), and the C1 controls U+0081, U+008D, U+008F,
# U+0090 and U+009D. Windows decodes the five bytes that cp1252 leaves undefined
# to these controls.
# MOJIBAKE_LEAD2 holds only the leads for Latin-1, Latin Extended-A, Greek and
# Cyrillic. The other 2-byte leads are real letters such as "ß" and "É", and
# real text such as "Spaß…" would match.
# The Makefile holds the pattern, so it is excluded, together with the lockfiles.
MOJIBAKE_CONT = (€|‚|ƒ|„|…|†|‡|ˆ|‰|Š|‹|Œ|Ž|‘|’|“|”|•|–|—|˜|™|š|›|œ|ž|Ÿ|¡|¢|£|¤|¥|¦|§|¨|©|ª|«|¬|®|¯|°|±|²|³|´|µ|¶|·|¸|¹|º|»|¼|½|¾|¿| ||||||)
MOJIBAKE_LEAD2 = (Â|Ã|Ä|Å|Î|Ï|Ð|Ñ)
MOJIBAKE_LEAD3 = (à|á|â|ã|ä|å|æ|ç|è|é|ê|ë|ì|í|î|ï)
MOJIBAKE_LEAD4 = (ð|ñ|ò|ó|ô)
MOJIBAKE_RE = $(MOJIBAKE_LEAD2)$(MOJIBAKE_CONT)|$(MOJIBAKE_LEAD3)$(MOJIBAKE_CONT)$(MOJIBAKE_CONT)|$(MOJIBAKE_LEAD4)$(MOJIBAKE_CONT)$(MOJIBAKE_CONT)$(MOJIBAKE_CONT)
check-mojibake:
@if git grep -nIE '$(MOJIBAKE_RE)' -- . ':!*pnpm-lock.yaml' ':!go.sum' ':!Makefile'; then \
echo "Mojibake found. Restore the original UTF-8 characters."; \
exit 1; \
fi
# Write the generated files: the automation rule JSON reference in the docs, the query builder field table, and the API key area table
generate:
go run ./internal/automationsgen
# Local pre-commit gate: mojibake check, generated files, fmt and gofix on changed files, then lint
precommit: check-mojibake generate fmt gofix-changed lint lint-ratchet typecheck-frontend
@echo "Pre-commit checks passed."
# Lint new Go issues since the develop merge-base, then all frontend files
lint:
@echo "Linting changed Go code..."
golangci-lint run --new-from-merge-base=develop --timeout=5m
@echo "Finding unused Go code in the whole repo..."
golangci-lint run --enable-only=unused --timeout=5m ./...
@echo "Linting frontend..."
cd $(WEB_DIR) && pnpm lint
# Full lint (entire codebase - use before commits/PRs)
lint-full:
@echo "Linting entire Go codebase..."
golangci-lint run --timeout=10m
@echo "Linting frontend..."
cd $(WEB_DIR) && pnpm lint
# Type-check the frontend with the command that the frontend build runs first
typecheck-frontend:
@echo "Type-checking frontend..."
cd $(WEB_DIR) && pnpm exec tsc -b
# Hold the linters in scripts/ratchet/golangci.yml to their shrink-only allowlist
lint-ratchet:
go run ./scripts/ratchet -base $$(git merge-base HEAD develop)
# Lint with JSON output (for AI agent consumption)
lint-json:
@echo "Generating lint report..."
golangci-lint run --new-from-merge-base=develop --output.json.path=./lint-report.json --timeout=5m || true
@echo "Lint report saved to lint-report.json"
# Lint with auto-fix where possible
lint-fix:
@echo "Running linters with auto-fix..."
golangci-lint run --fix --timeout=10m
cd $(WEB_DIR) && pnpm lint --fix
# Install development dependencies
deps:
@echo "Installing development dependencies..."
go mod download
cd $(WEB_DIR) && pnpm install
# Demo build of the frontend for the landing page (documentation/static/demo)
docs-demo:
@echo "Building the landing page demo..."
cd $(WEB_DIR) && pnpm install && pnpm build:demo
rm -rf documentation/static/demo && cp -r $(WEB_DIR)/dist-demo documentation/static/demo
# Documentation development server
docs-dev: docs-demo
@echo "Starting documentation development server..."
cd documentation && pnpm start
# Build documentation
docs-build: docs-demo
@echo "Building documentation..."
cd documentation && pnpm build
# Help
help:
@echo "Available targets:"
@echo ""
@echo "Build:"
@echo " make build - Build both frontend and backend"
@echo " make frontend - Build frontend only"
@echo " make backend - Build backend only"
@echo " make build/docker - Build Docker image"
@echo ""
@echo "Development:"
@echo " make dev - Run development servers (air + pnpm dev)"
@echo " make dev-backend - Run backend with hot reload"
@echo " make dev-frontend - Run frontend development server"
@echo " make dev-expose - Run frontend dev server exposed on 0.0.0.0"
@echo ""
@echo "Testing:"
@echo " make test - Run all Go tests with race detection"
@echo " make test-postgres - Run Postgres integration tests with a temporary server"
@echo " make test-frontend - Run frontend vitest suite"
@echo " make test-openapi - Validate OpenAPI specification"
@echo " make mutation - Mutation test changed Go lines and TypeScript files (MUTATION_BASE=origin/develop)"
@echo ""
@echo "Linting:"
@echo " make lint - Lint changed files only (fast, for iteration)"
@echo " make lint-full - Lint entire codebase"
@echo " make lint-ratchet - Check that the lint ratchet allowlist only shrinks"
@echo " make lint-json - Generate JSON lint report for AI agents"
@echo " make lint-fix - Auto-fix linting issues where possible"
@echo ""
@echo "Formatting:"
@echo " make fmt - Format changed files only (fast, for iteration)"
@echo " make gofix-changed - Apply go fix to changed Go files only"
@echo " make gofix-check-changed - Check go fix drift on changed Go files only"
@echo " make generate - Write generated files (automation rule JSON reference, query builder fields, API key areas)"
@echo " make precommit - Run local pre-commit gate (generate + fmt + gofix + lint + lint-ratchet + frontend type check)"
@echo ""
@echo "Documentation:"
@echo " make docs-dev - Run documentation development server"
@echo " make docs-build - Build documentation for production"
@echo ""
@echo "Other:"
@echo " make themes-fetch - Fetch the community themes"
@echo " make themes-clean - Clean the community themes"
@echo " make clean - Clean build artifacts"
@echo " make deps - Install dependencies"
@echo " make help - Show this help message"