-
Notifications
You must be signed in to change notification settings - Fork 33
Expand file tree
/
Copy pathMakefile
More file actions
831 lines (723 loc) · 38.1 KB
/
Copy pathMakefile
File metadata and controls
831 lines (723 loc) · 38.1 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
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
# Kubeli - Kubernetes Management Desktop App
# Makefile for common development tasks
.PHONY: help
.PHONY: dev web-dev tauri-dev
.PHONY: build build-start web-build tauri-build build-universal dmg build-dmg build-universal-dmg build-all build-windows
.PHONY: astro astro-build astro-public
.PHONY: build-deploy release release-push generate-changelog release-ai-dry-run release-changelog-dry-run release-manual-dry-run version-bump
.PHONY: lint format check rust-check rust-fmt rust-lint vet vet-install
.PHONY: test test-watch test-all test-e2e test-coverage test-coverage-frontend test-coverage-rust rust-test
.PHONY: clean clean-all install reinstall install-windows-build-deps deps update-deps
.PHONY: minikube-start minikube-stop minikube-status minikube-serve minikube-setup-samples minikube-clean-samples minikube-setup-flux minikube-setup-argocd minikube-setup-helm
.PHONY: minikube-setup-openshift minikube-clean-openshift minikube-setup-scale minikube-clean-scale
.PHONY: kubeconfig-setup-samples kubeconfig-clean-samples kubeconfig-fake-eks kubeconfig-fake-gke kubeconfig-fake-aks kubeconfig-auth-error kubeconfig-same-user kubeconfig-cleanup
.PHONY: k8s-pods k8s-services k8s-namespaces oidc-dev oidc-dev-stop oidc-e2e
.PHONY: sbom sbom-npm sbom-rust sbom-validate security-scan security-trivy security-semgrep
.PHONY: screenshots screenshot-setup screenshot-build
# Default target
.DEFAULT_GOAL := help
# Colors for output
CYAN := \033[36m
GREEN := \033[32m
YELLOW := \033[33m
RESET := \033[0m
TAURI_BUILD_ARGS ?=
## Development
dev: ## Start full Tauri development environment
npm run tauri:dev
web-dev: ## Start Vite web dev server only (no Tauri)
npm run dev
tauri-dev: ## Start Tauri development (alias for dev)
npm run tauri:dev
## Building
build: ## Build production Tauri app
@CURRENT_VERSION=$$(node -e "console.log(require('./package.json').version)"); \
echo "$(CYAN)Current version: $(GREEN)$$CURRENT_VERSION$(RESET)"; \
echo ""; \
if [ "$$BUMP_TYPE" = "patch" ] || [ "$$BUMP_TYPE" = "minor" ] || [ "$$BUMP_TYPE" = "major" ]; then \
echo "$(CYAN)Auto version bump via BUMP_TYPE=$$BUMP_TYPE$(RESET)"; \
echo ""; \
$(MAKE) version-bump TYPE=$$BUMP_TYPE; \
echo ""; \
elif [ "$$SKIP_VERSION_PROMPT" = "1" ]; then \
echo "$(CYAN)Skipping version prompt (SKIP_VERSION_PROMPT=1)$(RESET)"; \
echo ""; \
else \
printf "$(YELLOW)Do you want to bump the version before building? [y/N]: $(RESET)"; \
read answer; \
if [ "$$answer" = "y" ] || [ "$$answer" = "Y" ] || [ "$$answer" = "yes" ] || [ "$$answer" = "Yes" ]; then \
echo ""; \
$(MAKE) version-bump; \
echo ""; \
fi; \
fi; \
if [ -f .env ]; then \
set -a; source .env; set +a; \
fi; \
if [ -z "$$TAURI_SIGNING_PRIVATE_KEY" ] && [ -f ~/.tauri/kubeli.key ]; then \
export TAURI_SIGNING_PRIVATE_KEY="$$(cat ~/.tauri/kubeli.key)"; \
fi; \
if [ -z "$$TAURI_SIGNING_PRIVATE_KEY_PASSWORD" ]; then \
export TAURI_SIGNING_PRIVATE_KEY_PASSWORD=""; \
fi; \
echo "$(CYAN)Starting build...$(RESET)"; \
npm run tauri:build $(TAURI_BUILD_ARGS)
build-start: ## Build local app bundle and launch Kubeli.app (macOS)
@SKIP=$${SKIP_VERSION_PROMPT:-1}; \
BUILD_EXIT=0; \
$(MAKE) build SKIP_VERSION_PROMPT=$$SKIP BUMP_TYPE=$$BUMP_TYPE TAURI_BUILD_ARGS="-- --bundles app" || BUILD_EXIT=$$?; \
APP_PATH="src-tauri/target/release/bundle/macos/Kubeli.app"; \
if [ ! -d "$$APP_PATH" ]; then \
echo "$(YELLOW)Error: App bundle not found at $$APP_PATH$(RESET)"; \
exit $$BUILD_EXIT; \
fi; \
if [ $$BUILD_EXIT -ne 0 ]; then \
echo "$(YELLOW)Warning: Build exited with code $$BUILD_EXIT, but app bundle exists.$(RESET)"; \
fi; \
echo "$(CYAN)Opening $$APP_PATH...$(RESET)"; \
open "$$APP_PATH"; \
echo "$(GREEN)✓ Kubeli started$(RESET)"
web-build: ## Build Vite web app only
npm run build
tauri-build: ## Build Tauri app (alias for build)
npm run tauri:build
build-universal: ## Build Universal Binary (Apple Silicon + Intel)
@echo "$(CYAN)Building Universal Binary for macOS...$(RESET)"
@echo "$(YELLOW)Step 1: Building for Apple Silicon (arm64)...$(RESET)"
@cd src-tauri && cargo build --release --target aarch64-apple-darwin
@echo "$(YELLOW)Step 2: Building for Intel (x86_64)...$(RESET)"
@cd src-tauri && cargo build --release --target x86_64-apple-darwin
@echo "$(YELLOW)Step 3: Creating Universal Binary...$(RESET)"
@mkdir -p src-tauri/target/universal/release
@lipo -create \
src-tauri/target/aarch64-apple-darwin/release/kubeli \
src-tauri/target/x86_64-apple-darwin/release/kubeli \
-output src-tauri/target/universal/release/kubeli
@echo "$(YELLOW)Step 4: Copying Universal Binary to bundle...$(RESET)"
@cp src-tauri/target/universal/release/kubeli src-tauri/target/release/kubeli
@echo "$(YELLOW)Step 5: Rebuilding bundle with Universal Binary...$(RESET)"
@npm run tauri:build -- --bundles app
@echo "$(GREEN)✓ Universal Binary created successfully$(RESET)"
@file src-tauri/target/release/kubeli
dmg: ## Create DMG from built .app bundle
@echo "$(CYAN)Creating DMG from .app bundle...$(RESET)"
@APP_PATH="src-tauri/target/release/bundle/macos/Kubeli.app"; \
if [ ! -d "$$APP_PATH" ]; then \
echo "$(YELLOW)Error: App bundle not found at $$APP_PATH$(RESET)"; \
echo "$(YELLOW)Please run 'make build' first$(RESET)"; \
exit 1; \
fi; \
BINARY_PATH="$$APP_PATH/Contents/MacOS/kubeli"; \
if [ -f "$$BINARY_PATH" ]; then \
ARCH=$$(file "$$BINARY_PATH" | grep -o "arm64\|x86_64\|universal" | head -1 || echo "unknown"); \
if echo "$$ARCH" | grep -q "universal\|arm64.*x86_64"; then \
DMG_NAME="Kubeli_0.1.0_universal.dmg"; \
elif echo "$$ARCH" | grep -q "arm64"; then \
DMG_NAME="Kubeli_0.1.0_aarch64.dmg"; \
elif echo "$$ARCH" | grep -q "x86_64"; then \
DMG_NAME="Kubeli_0.1.0_x86_64.dmg"; \
else \
DMG_NAME="Kubeli_0.1.0.dmg"; \
fi; \
else \
DMG_NAME="Kubeli_0.1.0.dmg"; \
fi; \
DMG_PATH="src-tauri/target/release/bundle/dmg/$$DMG_NAME"; \
mkdir -p "$$(dirname $$DMG_PATH)"; \
echo "$(CYAN)Creating DMG: $$DMG_PATH$(RESET)"; \
hdiutil create -volname "Kubeli" -srcfolder "$$APP_PATH" -ov -format UDZO "$$DMG_PATH"; \
if [ $$? -eq 0 ]; then \
echo "$(GREEN)✓ DMG created successfully: $$DMG_PATH$(RESET)"; \
else \
echo "$(YELLOW)✗ Failed to create DMG$(RESET)"; \
exit 1; \
fi
build-dmg: build dmg ## Build app and create DMG
build-universal-dmg: build-universal dmg ## Build Universal Binary and create DMG
build-all: build build-windows ## Build both macOS and Windows installers
@echo "$(GREEN)✓ Both macOS and Windows builds complete$(RESET)"
## Astro Landing Page (web/)
astro: ## Start Astro dev server for landing page
@echo "$(CYAN)Starting Astro dev server...$(RESET)"
cd web && bun run dev
astro-build: ## Build Astro landing page
@echo "$(CYAN)Building Astro landing page...$(RESET)"
@if [ ! -d "web/node_modules" ]; then \
echo "$(YELLOW)Installing web dependencies...$(RESET)"; \
cd web && bun install; \
fi
cd web && bun run build
@echo "$(GREEN)✓ Astro build complete (web/dist/)$(RESET)"
astro-public: astro-build ## Build and deploy landing page to FTP
@echo "$(CYAN)Deploying Astro landing page to FTP...$(RESET)"
@if [ -f .env ]; then \
set -a; source .env; set +a; \
fi; \
if [ ! -d "web/dist" ]; then \
echo "$(YELLOW)Error: web/dist not found. Run 'make astro-build' first.$(RESET)"; \
exit 1; \
fi; \
echo "$(CYAN)Uploading to $$DEPLOY_LANDING_URL...$(RESET)"; \
cd web/dist && for file in $$(find . -type f); do \
echo " Uploading: $$file"; \
curl -s --ftp-create-dirs -T "$$file" --user "$$FTP_USER:$$FTP_PASSWORD" "ftp://$$FTP_HOST$$DEPLOY_LANDING_FTP_PATH/$$file"; \
done; \
echo "$(GREEN)✓ Landing page deployed to https://$$DEPLOY_LANDING_URL$(RESET)"
## Release & Deployment
build-deploy: release ## Release via CI (version bump, changelog, commit, tag, push)
release: ## Release: version bump, changelog, commit, tag push → CI builds all platforms
@$(MAKE) version-bump
@echo ""
@$(MAKE) generate-changelog
@echo ""
@$(MAKE) release-push
release-push: ## Review and publish an already prepared release (requires confirmation)
@VERSION=$$(node -e "console.log(require('./package.json').version)"); \
if [ ! -f .release-notes.md ]; then \
echo "$(YELLOW)Error: .release-notes.md not found. Run 'make generate-changelog' first.$(RESET)"; \
exit 1; \
fi; \
if git rev-parse --verify "refs/tags/v$$VERSION" >/dev/null 2>&1; then \
echo "$(YELLOW)Error: tag v$$VERSION already exists.$(RESET)"; \
exit 1; \
fi; \
echo "$(CYAN)Release v$$VERSION is prepared for review.$(RESET)"; \
echo ""; \
echo "$(CYAN)Changed release files:$(RESET)"; \
git --no-pager diff --stat -- package.json package-lock.json src-tauri/Cargo.toml src-tauri/Cargo.lock src-tauri/tauri.conf.json src-tauri/gen/schemas/ CHANGELOG.md web/src/data/changelog.md .release-notes.md; \
echo ""; \
echo "$(CYAN)GitHub release notes preview:$(RESET)"; \
echo "$(YELLOW)----------------------------------------$(RESET)"; \
cat .release-notes.md; \
echo ""; \
echo "$(YELLOW)----------------------------------------$(RESET)"; \
echo "$(CYAN)Review or edit the files now. Nothing has been committed or pushed yet.$(RESET)"; \
printf "$(YELLOW)Press Enter to commit and push, or type anything to abort: $(RESET)"; \
if ! IFS= read -r approval; then \
echo ""; \
echo "$(YELLOW)No interactive confirmation received. Release aborted.$(RESET)"; \
exit 1; \
fi; \
if [ -n "$$approval" ]; then \
echo "$(YELLOW)Release aborted. Prepared files were left unchanged.$(RESET)"; \
echo "$(CYAN)After reviewing them, run 'make release-push' to continue.$(RESET)"; \
exit 1; \
fi; \
echo ""; \
echo "$(CYAN)Committing release...$(RESET)"; \
git add package.json package-lock.json src-tauri/Cargo.toml src-tauri/Cargo.lock src-tauri/tauri.conf.json src-tauri/gen/schemas/ CHANGELOG.md web/src/data/changelog.md .release-notes.md; \
git commit -m "chore(release): bump version to $$VERSION and update changelog"; \
echo "$(CYAN)Pushing to remote...$(RESET)"; \
git push; \
echo "$(CYAN)Creating and pushing tag v$$VERSION...$(RESET)"; \
git tag "v$$VERSION"; \
git push origin "v$$VERSION"; \
echo ""; \
echo "$(GREEN)✓ Release v$$VERSION triggered!$(RESET)"; \
echo "$(CYAN)CI will now build macOS, Windows, and Linux.$(RESET)"; \
echo "$(CYAN)Watch progress: https://github.com/atilladeniz/Kubeli/actions$(RESET)"
generate-changelog: ## Generate changelog using Claude, Codex, or OpenCode CLI
@echo "$(CYAN)Generating changelog with AI CLI fallback (Claude → Codex → OpenCode)...$(RESET)"
@node scripts/generate-changelog.js
@if [ -f .release-notes.md ]; then \
echo "$(GREEN)✓ Changelog files updated$(RESET)"; \
fi
release-ai-dry-run: ## Test release AI providers without changing release files (optional: PROVIDER=codex)
@echo "$(CYAN)Testing release AI providers (no release files will be changed)...$(RESET)"
@PROVIDER="$(PROVIDER)" node scripts/generate-changelog.js --dry-run
release-changelog-dry-run: ## Test the Claude → Codex → OpenCode fallback chain without changing release files
@echo "$(CYAN)Testing the release changelog fallback chain...$(RESET)"
@node scripts/generate-changelog.js --fallback-dry-run
release-manual-dry-run: ## Test the manual Markdown copy/paste fallback without changing release files
@echo "$(CYAN)Testing the manual Markdown changelog fallback...$(RESET)"
@node scripts/generate-changelog.js --manual-dry-run
## Code Quality
lint: ## Run ESLint
npm run lint
format: ## Format code with Prettier
npx prettier --write "src/**/*.{ts,tsx,js,jsx,json,css}"
check: ## Run type checking
npm run typecheck
rust-check: ## Check Rust code
cd src-tauri && cargo check
rust-fmt: ## Format Rust code
cd src-tauri && cargo fmt
rust-lint: ## Lint Rust code with clippy
cd src-tauri && cargo clippy
## Testing
test: ## Run all tests
npm run test
test-watch: ## Run tests in watch mode
npm run test:watch
test-all: ## Run frontend, backend, and E2E tests
npm run test
cd src-tauri && cargo test
npm run test:e2e
test-e2e: ## Run Playwright E2E tests
npm run test:e2e
test-coverage: ## Run all tests with coverage
@echo "$(CYAN)Running frontend tests with coverage...$(RESET)"
npm run test:coverage
@echo ""
@echo "$(CYAN)Running Rust tests with coverage...$(RESET)"
cd src-tauri && cargo llvm-cov --lcov --output-path ../coverage-rust.lcov
@echo ""
@echo "$(GREEN)Coverage reports generated:$(RESET)"
@echo " Frontend: coverage/index.html"
@echo " Rust: coverage-rust.lcov"
test-coverage-frontend: ## Run frontend tests with coverage
npm run test:coverage
test-coverage-rust: ## Run Rust tests with coverage
cd src-tauri && cargo llvm-cov --html --output-dir ../coverage-rust
@echo "$(GREEN)Rust coverage report: coverage-rust/html/index.html$(RESET)"
rust-test: ## Run Rust tests
cd src-tauri && cargo test
## Cleanup
clean: ## Clean build artifacts
rm -rf dist
rm -rf node_modules/.cache
cd src-tauri && cargo clean
clean-all: clean ## Deep clean including node_modules
rm -rf node_modules
rm -rf src-tauri/target
## Installation & Windows Builds
install: ## Install all dependencies
npm install
cd src-tauri && cargo fetch
@$(MAKE) vet-install || echo "$(YELLOW)vet installation skipped (optional, requires Python)$(RESET)"
install-windows-build-deps: ## Install dependencies for cross-compiling Windows builds on macOS
@echo "$(CYAN)Installing Windows cross-compile dependencies...$(RESET)"
@which brew > /dev/null || (echo "$(YELLOW)Homebrew not found. Please install from https://brew.sh$(RESET)" && exit 1)
brew install nsis llvm lld cmake ninja nasm
rustup target add x86_64-pc-windows-msvc
cargo install --locked cargo-xwin
@echo "$(GREEN)✓ Windows build dependencies installed$(RESET)"
@echo ""
@echo "$(CYAN)To build for Windows, run:$(RESET)"
@echo " make build-windows"
build-windows: ## Cross-compile Windows NSIS installer from macOS
@echo "$(CYAN)Building Windows installer (cross-compile)...$(RESET)"
@if [ -f .env ]; then set -a; source .env; set +a; fi; \
export PATH="/opt/homebrew/opt/llvm/bin:$$PATH" && \
npm run tauri build -- --runner cargo-xwin --target x86_64-pc-windows-msvc
@echo "$(GREEN)✓ Windows installer built$(RESET)"
@VERSION=$$(node -e "console.log(require('./package.json').version)"); \
NSIS_DIR="src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis"; \
EXE_FILE="$$NSIS_DIR/Kubeli_$${VERSION}_x64-setup.exe"; \
if [ -f "$$EXE_FILE" ]; then \
if [ -f .env ]; then \
set -a; source .env; set +a; \
fi; \
if [ -z "$$TAURI_SIGNING_PRIVATE_KEY" ] && [ -f ~/.tauri/kubeli.key ]; then \
export TAURI_SIGNING_PRIVATE_KEY="$$(cat ~/.tauri/kubeli.key)"; \
fi; \
if [ -n "$$TAURI_SIGNING_PRIVATE_KEY" ]; then \
echo "$(CYAN)Signing Windows installer for auto-updates...$(RESET)"; \
TAURI_PRIVATE_KEY="$$TAURI_SIGNING_PRIVATE_KEY" \
TAURI_PRIVATE_KEY_PASSWORD="$${TAURI_SIGNING_PRIVATE_KEY_PASSWORD:-}" \
npx tauri signer sign -p "$${TAURI_SIGNING_PRIVATE_KEY_PASSWORD:-}" "$$EXE_FILE"; \
echo "$(GREEN)✓ Signed $$EXE_FILE.sig$(RESET)"; \
else \
echo "$(YELLOW)Warning: TAURI_SIGNING_PRIVATE_KEY not set, skipping signature$(RESET)"; \
fi; \
fi
@echo "$(CYAN)Output: src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis/$(RESET)"
reinstall: clean-all install ## Clean and reinstall all dependencies
## Windows VM Testing (Remote Kubernetes)
minikube-serve: ## Expose minikube API for Windows VM testing (run on Mac)
@echo "$(CYAN)Starting minikube API proxy for remote access...$(RESET)"
@if ! minikube status > /dev/null 2>&1; then \
echo "$(YELLOW)Minikube not running. Starting...$(RESET)"; \
$(MAKE) minikube-start; \
fi
@HOST_IP=$$(ipconfig getifaddr en0 2>/dev/null || ipconfig getifaddr en1 2>/dev/null || echo "0.0.0.0"); \
echo "$(GREEN)✓ Minikube is running$(RESET)"; \
echo ""; \
echo "$(CYAN)============================================$(RESET)"; \
echo "$(CYAN) Kubernetes API Proxy for Windows VM $(RESET)"; \
echo "$(CYAN)============================================$(RESET)"; \
echo ""; \
echo "$(YELLOW)Mac IP:$(RESET) $$HOST_IP"; \
echo "$(YELLOW)Proxy Port:$(RESET) 8001"; \
echo ""; \
echo "$(CYAN)In Windows VM (UTM shared folder), run:$(RESET)"; \
echo ""; \
printf ' cd <SharedDrive>:\\.dev\\windows\n'; \
printf ' .\\connect-minikube.ps1 -HostIP %s\n' "$$HOST_IP"; \
echo ""; \
echo "$(CYAN)Example with Z: drive:$(RESET)"; \
echo ""; \
printf ' cd Z:\\.dev\\windows\n'; \
printf ' .\\connect-minikube.ps1 -HostIP %s\n' "$$HOST_IP"; \
echo ""; \
echo "$(YELLOW)Press Ctrl+C to stop the proxy$(RESET)"; \
echo ""; \
kubectl proxy --address='0.0.0.0' --port=8001 --accept-hosts='.*'
## Kubernetes (for local development)
# Second cluster brought up alongside the primary one so multi-cluster
# port-forwarding (#388) works out of the box. Set WITH_SECOND_CLUSTER=0 to
# start only the primary minikube profile.
WITH_SECOND_CLUSTER ?= 1
SECOND_CLUSTER ?= kubeli-nonprod
minikube-start: ## Start minikube (+ a second cluster) with addons and sample resources
@echo "$(CYAN)Starting minikube...$(RESET)"
@minikube start
@echo "$(CYAN)Enabling metrics-server addon...$(RESET)"
@minikube addons enable metrics-server
@echo "$(CYAN)Enabling ingress addon...$(RESET)"
@minikube addons enable ingress
@echo "$(CYAN)Waiting for ingress controller to be ready...$(RESET)"
@kubectl wait --namespace ingress-nginx --for=condition=ready pod --selector=app.kubernetes.io/component=controller --timeout=120s 2>/dev/null || true
@echo "$(CYAN)Applying sample Kubernetes resources...$(RESET)"
@$(MAKE) minikube-setup-samples
@echo "$(CYAN)Setting up Flux test resources...$(RESET)"
@$(MAKE) minikube-setup-flux
@echo "$(CYAN)Setting up ArgoCD test resources...$(RESET)"
@$(MAKE) minikube-setup-argocd
@echo "$(CYAN)Setting up native Helm releases...$(RESET)"
@$(MAKE) minikube-setup-helm
@echo "$(GREEN)✓ Minikube ready with sample resources$(RESET)"
@if [ "$(WITH_SECOND_CLUSTER)" = "1" ]; then \
echo ""; \
echo "$(CYAN)Starting second cluster '$(SECOND_CLUSTER)' for multi-cluster port-forward testing...$(RESET)"; \
minikube start -p $(SECOND_CLUSTER) || exit 1; \
echo "$(CYAN)Applying forward target (kubeli-pf/pf-web) to $(SECOND_CLUSTER)...$(RESET)"; \
kubectl --context $(SECOND_CLUSTER) apply -f .dev/k8s-samples/20-portforward-target.yaml; \
kubectl --context $(SECOND_CLUSTER) -n kubeli-pf rollout status deploy/pf-web --timeout=90s || true; \
echo "$(GREEN)✓ Two clusters ready: minikube and $(SECOND_CLUSTER)$(RESET)"; \
echo "$(CYAN)Forward Service kubeli-pf/pf-web:80 in each, then switch clusters — forwards stay alive.$(RESET)"; \
fi
minikube-setup-samples: ## Apply sample Kubernetes resources for testing
@echo "$(CYAN)Applying sample manifests from .dev/k8s-samples/...$(RESET)"
@if [ -d ".dev/k8s-samples" ]; then \
kubectl apply -f .dev/k8s-samples/01-namespace.yaml 2>/dev/null || true; \
sleep 1; \
kubectl apply -f .dev/k8s-samples/ 2>&1 | grep -vE "unchanged|resource mapping not found|ensure CRDs are installed first" || true; \
echo "$(GREEN)✓ Sample resources applied$(RESET)"; \
echo ""; \
echo "$(CYAN)Resources created in kubeli-demo namespace:$(RESET)"; \
echo " - Deployments: demo-web (3), demo-api (2), demo-frontend (1), demo-auth (1)"; \
echo " - StatefulSet: demo-db"; \
echo " - DaemonSet: demo-log-collector"; \
echo " - Job: demo-migration, CronJob: demo-cleanup"; \
echo " - Ingresses: demo-web-ingress, demo-secure-ingress"; \
echo " - NetworkPolicies: 4 policies"; \
echo " - HPAs: demo-web-hpa, demo-api-hpa (v2)"; \
echo " - PDBs: demo-web-pdb, demo-api-pdb"; \
echo " - PVs: 10 volumes (100Mi-256Gi), PVC: demo-pvc"; \
echo " - RBAC: Roles, RoleBindings, ServiceAccount"; \
echo " - Quotas: ResourceQuota, LimitRange"; \
echo " - Flux HelmReleases: podinfo, redis, prometheus-stack, cert-manager"; \
else \
echo "$(YELLOW)Warning: .dev/k8s-samples/ directory not found$(RESET)"; \
fi
minikube-setup-flux: ## Install Flux CRDs and sample HelmReleases for testing
@echo "$(CYAN)Installing Flux CRDs...$(RESET)"
@kubectl apply -f .dev/k8s-samples/11-flux-crds.yaml 2>/dev/null || true
@sleep 2
@echo "$(CYAN)Creating sample Flux resources...$(RESET)"
@kubectl apply -f .dev/k8s-samples/12-flux-helmreleases.yaml 2>/dev/null || true
@kubectl apply -f .dev/k8s-samples/13-flux-kustomizations.yaml 2>/dev/null || true
@sleep 1
@echo "$(CYAN)Setting resource statuses...$(RESET)"
@kubectl patch helmrelease podinfo -n kubeli-demo --type=merge --subresource=status \
-p '{"status":{"conditions":[{"type":"Ready","status":"True","reason":"ReconciliationSucceeded","message":"Release reconciliation succeeded","lastTransitionTime":"2026-01-20T10:00:00Z"}],"lastAppliedRevision":"6.5.0","lastAttemptedRevision":"6.5.0","history":[{"chartVersion":"6.5.0","appVersion":"6.5.0","status":"deployed"}]}}' 2>/dev/null || true
@kubectl patch helmrelease redis -n kubeli-demo --type=merge --subresource=status \
-p '{"status":{"conditions":[{"type":"Ready","status":"True","reason":"ReconciliationSucceeded","message":"Release reconciliation succeeded","lastTransitionTime":"2026-01-20T09:30:00Z"}],"lastAppliedRevision":"18.6.1","lastAttemptedRevision":"18.6.1","history":[{"chartVersion":"18.6.1","appVersion":"7.2.4","status":"deployed"}]}}' 2>/dev/null || true
@kubectl patch helmrelease prometheus-stack -n kubeli-demo --type=merge --subresource=status \
-p '{"status":{"conditions":[{"type":"Ready","status":"False","reason":"ReconciliationFailed","message":"Helm upgrade failed: timed out waiting for resources","lastTransitionTime":"2026-01-20T11:00:00Z"}],"lastAttemptedRevision":"55.5.0","history":[{"chartVersion":"55.5.0","appVersion":"2.49.1","status":"failed"}]}}' 2>/dev/null || true
@kubectl patch helmrelease cert-manager -n kubeli-demo --type=merge --subresource=status \
-p '{"status":{"conditions":[{"type":"Ready","status":"True","reason":"ReconciliationSucceeded","message":"Release reconciliation succeeded","lastTransitionTime":"2026-01-20T08:00:00Z"}],"lastAppliedRevision":"1.13.3","lastAttemptedRevision":"1.13.3","history":[{"chartVersion":"1.13.3","appVersion":"1.13.3","status":"deployed"}]}}' 2>/dev/null || true
@kubectl patch kustomization apps -n kubeli-demo --type=merge --subresource=status \
-p '{"status":{"conditions":[{"type":"Ready","status":"True","reason":"ReconciliationSucceeded","message":"Applied revision: main@sha1:abc123","lastTransitionTime":"2026-01-20T10:00:00Z"}],"lastAppliedRevision":"main@sha1:abc123"}}' 2>/dev/null || true
@kubectl patch kustomization infrastructure -n kubeli-demo --type=merge --subresource=status \
-p '{"status":{"conditions":[{"type":"Ready","status":"True","reason":"ReconciliationSucceeded","message":"Applied revision: main@sha1:def456","lastTransitionTime":"2026-01-20T09:00:00Z"}],"lastAppliedRevision":"main@sha1:def456"}}' 2>/dev/null || true
@kubectl patch kustomization monitoring -n kubeli-demo --type=merge --subresource=status \
-p '{"status":{"conditions":[{"type":"Ready","status":"False","reason":"ReconciliationFailed","message":"kustomize build failed: missing resources","lastTransitionTime":"2026-01-20T11:00:00Z"}]}}' 2>/dev/null || true
@echo "$(GREEN)✓ Flux test resources installed$(RESET)"
@echo ""
@echo "$(CYAN)Flux HelmReleases:$(RESET)"
@echo " - podinfo (Ready)"
@echo " - redis (Ready)"
@echo " - prometheus-stack (Failed)"
@echo " - cert-manager (Ready)"
@echo ""
@echo "$(CYAN)Flux Kustomizations:$(RESET)"
@echo " - apps (Ready)"
@echo " - infrastructure (Ready)"
@echo " - monitoring (Failed)"
@echo ""
@echo "$(YELLOW)Note: Mock resources for testing Kubeli's Flux support.$(RESET)"
minikube-setup-argocd: ## Install ArgoCD CRD and sample Applications for testing
@echo "$(CYAN)Installing ArgoCD CRD...$(RESET)"
@kubectl apply -f .dev/k8s-samples/17-argocd-crds.yaml 2>/dev/null || true
@echo "$(CYAN)Waiting for ArgoCD CRD to be established...$(RESET)"
@kubectl wait --for condition=established --timeout=60s crd/applications.argoproj.io 2>/dev/null || true
@echo "$(CYAN)Creating sample ArgoCD Applications...$(RESET)"
@kubectl apply -f .dev/k8s-samples/18-argocd-applications.yaml 2>/dev/null || true
@echo "$(GREEN)✓ ArgoCD test resources installed$(RESET)"
@echo ""
@echo "$(CYAN)ArgoCD Applications (kubeli-demo namespace):$(RESET)"
@echo " - demo-frontend (Synced / Healthy)"
@echo " - demo-backend (OutOfSync / Progressing)"
@echo " - demo-monitoring (OutOfSync / Degraded)"
@echo ""
@echo "$(YELLOW)Note: Minimal CRD + mock resources for testing Kubeli's ArgoCD support.$(RESET)"
minikube-setup-helm: ## Install native Helm releases for testing (requires helm CLI)
@echo "$(CYAN)Installing native Helm releases...$(RESET)"
@if command -v helm >/dev/null 2>&1; then \
echo "$(CYAN)Adding Helm repositories...$(RESET)"; \
helm repo add bitnami https://charts.bitnami.com/bitnami >/dev/null 2>&1 || true; \
helm repo update >/dev/null 2>&1 || true; \
echo "$(CYAN)Installing nginx chart...$(RESET)"; \
helm upgrade --install demo-nginx bitnami/nginx \
--namespace kubeli-demo \
--set replicaCount=1 \
--set service.type=ClusterIP \
--timeout 60s >/dev/null 2>&1 && echo " $(GREEN)✓ demo-nginx$(RESET)" || echo " $(YELLOW)✗ demo-nginx (failed)$(RESET)"; \
echo "$(CYAN)Installing mysql chart...$(RESET)"; \
helm upgrade --install demo-mysql bitnami/mysql \
--namespace kubeli-demo \
--set auth.rootPassword=testpassword \
--set primary.persistence.enabled=false \
--timeout 60s >/dev/null 2>&1 && echo " $(GREEN)✓ demo-mysql$(RESET)" || echo " $(YELLOW)✗ demo-mysql (failed)$(RESET)"; \
echo "$(GREEN)✓ Native Helm releases installed$(RESET)"; \
else \
echo "$(YELLOW)Warning: helm CLI not found. Skipping native Helm releases.$(RESET)"; \
echo "$(YELLOW)Install helm to test native Helm releases: https://helm.sh/docs/intro/install/$(RESET)"; \
fi
minikube-clean-samples: ## Remove sample Kubernetes resources
@echo "$(CYAN)Removing sample resources...$(RESET)"
@if command -v helm >/dev/null 2>&1; then \
helm uninstall demo-nginx -n kubeli-demo 2>/dev/null || true; \
helm uninstall demo-mysql -n kubeli-demo 2>/dev/null || true; \
fi
@kubectl delete namespace kubeli-demo --ignore-not-found=true
@kubectl delete pv demo-pv-100mi demo-pv-500mi demo-pv-1gi demo-pv-2gi demo-pv-5gi demo-pv-10gi demo-pv-20gi demo-pv-50gi demo-pv-100gi demo-pv-256gi --ignore-not-found=true
@kubectl delete ingressclass demo-ingress-class --ignore-not-found=true
@echo "$(GREEN)✓ Sample resources removed$(RESET)"
minikube-stop: ## Stop minikube cluster (and the second cluster if started)
@minikube stop
@if [ "$(WITH_SECOND_CLUSTER)" = "1" ]; then \
minikube stop -p $(SECOND_CLUSTER) 2>/dev/null || true; \
fi
minikube-status: ## Check minikube status
@minikube status
@echo ""
@echo "$(CYAN)Sample resources status:$(RESET)"
@kubectl get pods -n kubeli-demo --no-headers 2>/dev/null | wc -l | xargs -I{} echo " Pods in kubeli-demo: {}" || echo " kubeli-demo namespace not found"
## Local Testing Lab (optional scenarios)
minikube-setup-openshift: ## Install OpenShift CRDs and sample resources (Routes, DeploymentConfigs)
@echo "$(CYAN)Installing OpenShift CRDs...$(RESET)"
@kubectl apply -f .dev/k8s-samples/14-openshift-crds.yaml 2>/dev/null || true
@sleep 2
@echo "$(CYAN)Creating sample OpenShift resources...$(RESET)"
@kubectl apply -f .dev/k8s-samples/15-openshift-samples.yaml 2>/dev/null || true
@echo "$(GREEN)✓ OpenShift test resources installed$(RESET)"
@echo ""
@echo "$(CYAN)OpenShift Resources:$(RESET)"
@echo " - Project: kubeli-openshift-demo"
@echo " - Routes: demo-web-route, demo-secure-route, demo-api-route"
@echo " - DeploymentConfigs: demo-web-dc, demo-api-dc"
@echo ""
@echo "$(YELLOW)Note: Mock resources for testing Kubeli's OpenShift detection.$(RESET)"
minikube-clean-openshift: ## Remove OpenShift test resources
@echo "$(CYAN)Removing OpenShift test resources...$(RESET)"
@kubectl delete namespace kubeli-openshift-demo --ignore-not-found=true
@kubectl delete project kubeli-openshift-demo --ignore-not-found=true 2>/dev/null || true
@echo "$(GREEN)✓ OpenShift test resources removed$(RESET)"
minikube-setup-scale: ## Create N dummy pods for scale testing (default N=100)
@echo "$(CYAN)Creating scale-test pods...$(RESET)"
@./scripts/k8s-scale.sh create $(or $(N),100)
minikube-clean-scale: ## Remove all scale-test pods
@echo "$(CYAN)Removing scale-test pods...$(RESET)"
@./scripts/k8s-scale.sh delete
@kubectl delete namespace kubeli-scale-test --ignore-not-found=true 2>/dev/null || true
@echo "$(GREEN)✓ Scale-test resources removed$(RESET)"
kubeconfig-setup-samples: ## Copy sample kubeconfig files to ~/.kube/kubeli-samples/ for testing
@echo "$(CYAN)Setting up sample kubeconfig sources...$(RESET)"
@mkdir -p ~/.kube/kubeli-samples/incomplete
@cp .dev/kubeconfig-samples/config-minikube.yaml ~/.kube/kubeli-samples/
@cp .dev/kubeconfig-samples/config-cloud.yaml ~/.kube/kubeli-samples/
@cp .dev/kubeconfig-samples/config-azure.yaml ~/.kube/kubeli-samples/
@cp .dev/kubeconfig-samples/incomplete/*.yaml ~/.kube/kubeli-samples/incomplete/
@echo "$(GREEN)✓ Sample kubeconfigs copied to ~/.kube/kubeli-samples/$(RESET)"
@echo ""
@echo "$(CYAN)Files:$(RESET)"
@echo " ~/.kube/kubeli-samples/config-minikube.yaml (1 context: minikube)"
@echo " ~/.kube/kubeli-samples/config-cloud.yaml (2 contexts: aws-staging, aws-production)"
@echo " ~/.kube/kubeli-samples/config-azure.yaml (1 context: aks-dev)"
@echo " ~/.kube/kubeli-samples/incomplete/ (3 files for merge mode testing)"
@echo ""
@echo "$(YELLOW)Add these as sources in Kubeli Settings > Kubeconfig tab$(RESET)"
kubeconfig-clean-samples: ## Remove sample kubeconfig files from ~/.kube/kubeli-samples/
@echo "$(CYAN)Removing sample kubeconfig sources...$(RESET)"
@rm -rf ~/.kube/kubeli-samples
@echo "$(GREEN)✓ Sample kubeconfigs removed$(RESET)"
kubeconfig-fake-eks: ## Create fake EKS context pointing to local cluster
@./scripts/kubeconfig-sim.sh create-eks
kubeconfig-fake-gke: ## Create fake GKE context pointing to local cluster
@./scripts/kubeconfig-sim.sh create-gke
kubeconfig-fake-aks: ## Create fake AKS context pointing to local cluster
@./scripts/kubeconfig-sim.sh create-aks
kubeconfig-auth-error: ## Create context with invalid token for auth error testing
@./scripts/kubeconfig-sim.sh create-auth-error
kubeconfig-same-user: ## Start 3 minikube profiles with same "admin" user for #283 testing
@./scripts/kubeconfig-sim.sh create-same-user
kubeconfig-cleanup: ## Remove all kubeli-* simulated contexts, profiles, and files
@./scripts/kubeconfig-sim.sh cleanup
k8s-pods: ## List all pods across namespaces
kubectl get pods -A
k8s-services: ## List all services across namespaces
kubectl get services -A
k8s-namespaces: ## List all namespaces
kubectl get namespaces
## OIDC Sign-in Testing
oidc-dev: ## Start local OIDC stack (HTTPS Dex + minikube that trusts it) to test sign-in
@./scripts/oidc-dev.sh up
oidc-dev-stop: ## Tear down the local OIDC stack (Dex + minikube profile + context)
@./scripts/oidc-dev.sh down
oidc-e2e: ## Run the browserless OIDC end-to-end test against the local Dex (needs make oidc-dev; minikube not required)
@curl -sf --cacert .dev/oidc/certs/ca.crt https://host.minikube.internal:5556/dex/.well-known/openid-configuration >/dev/null \
|| { echo "Dex is not reachable — run 'make oidc-dev' first (the Dex container is enough)."; exit 1; }
@mkdir -p dist
cd src-tauri && cargo test --bin kubeli live_tests -- --ignored --nocapture
## Software Bill of Materials (SBOM)
sbom-npm: ## Generate npm SBOM (CycloneDX JSON)
npm run sbom:npm
sbom-rust: ## Generate Rust SBOM (CycloneDX JSON)
cd src-tauri && cargo cyclonedx --format json --spec-version 1.5 --no-build-deps --override-filename sbom-rust
mv src-tauri/sbom-rust.json sbom-rust.json
sbom: sbom-npm sbom-rust ## Generate both SBOM files
sbom-validate: sbom ## Generate and validate SBOMs with cyclonedx-cli
@echo "$(CYAN)Validating SBOMs against CycloneDX 1.5 schema...$(RESET)"
docker run --rm --platform linux/amd64 -v $(PWD):/data cyclonedx/cyclonedx-cli validate --input-file /data/sbom-npm.json --input-version v1_5 --fail-on-errors
docker run --rm --platform linux/amd64 -v $(PWD):/data cyclonedx/cyclonedx-cli validate --input-file /data/sbom-rust.json --input-version v1_5 --fail-on-errors
@echo "$(GREEN)✓ Both SBOMs validated$(RESET)"
## Security Scanning
security-scan: sbom security-trivy security-semgrep ## Run all security scans
security-trivy: ## Scan SBOMs for vulnerabilities with Trivy (requires Docker)
@echo "$(CYAN)Scanning npm SBOM for vulnerabilities...$(RESET)"
docker run --rm --platform linux/amd64 -v $(PWD):/data aquasec/trivy:0.68.1 sbom /data/sbom-npm.json --severity HIGH,CRITICAL
@echo "$(CYAN)Scanning Rust SBOM for vulnerabilities...$(RESET)"
docker run --rm --platform linux/amd64 -v $(PWD):/data aquasec/trivy:0.68.1 sbom /data/sbom-rust.json --severity HIGH,CRITICAL
@echo "$(CYAN)Scanning filesystem for secrets and misconfigs...$(RESET)"
docker run --rm --platform linux/amd64 -v $(PWD):/data aquasec/trivy:0.68.1 fs /data --scanners secret,misconfig --severity HIGH,CRITICAL
@echo "$(GREEN)✓ Trivy scans completed$(RESET)"
security-semgrep: ## Run Semgrep SAST scan (requires Docker)
@echo "$(CYAN)Running Semgrep SAST scan...$(RESET)"
docker run --rm --platform linux/amd64 -v $(PWD):/src semgrep/semgrep:1.112.0 semgrep scan --config p/default --config p/secrets --config p/typescript --config p/react --config p/rust --config /src/.semgrep.yaml --metrics off
@echo "$(GREEN)✓ Semgrep scan completed$(RESET)"
## Versioning & Dependencies
version-bump: ## Bump version interactively (or use TYPE=patch|minor|major)
@TYPE="$(TYPE)"; \
if [ -z "$$TYPE" ]; then \
CURRENT_VERSION=$$(node -e "console.log(require('./package.json').version)"); \
echo "$(CYAN)Current version: $(GREEN)$$CURRENT_VERSION$(RESET)"; \
echo ""; \
echo "$(YELLOW)Select version bump type:$(RESET)"; \
echo " $(GREEN)1$(RESET)) patch (e.g., 0.1.5 → 0.1.6)"; \
echo " $(GREEN)2$(RESET)) minor (e.g., 0.1.5 → 0.2.0)"; \
echo " $(GREEN)3$(RESET)) major (e.g., 0.1.5 → 1.0.0)"; \
echo ""; \
printf "$(CYAN)Enter choice [1-3]: $(RESET)"; \
read choice; \
case $$choice in \
1) TYPE=patch ;; \
2) TYPE=minor ;; \
3) TYPE=major ;; \
*) echo "$(YELLOW)Invalid choice. Exiting.$(RESET)"; exit 1 ;; \
esac; \
else \
case $$TYPE in \
1|patch) TYPE=patch ;; \
2|minor) TYPE=minor ;; \
3|major) TYPE=major ;; \
*) echo "$(YELLOW)Error: TYPE must be 1/patch, 2/minor, or 3/major$(RESET)"; exit 1 ;; \
esac; \
fi; \
echo "$(CYAN)Bumping version ($$TYPE)...$(RESET)"; \
OLD_VERSION=$$(node -e "console.log(require('./package.json').version)"); \
npm version $$TYPE --no-git-tag-version; \
NEW_VERSION=$$(node -e "console.log(require('./package.json').version)"); \
echo "$(CYAN)Updating Cargo.toml...$(RESET)"; \
sed -i '' "s/version = \"$$OLD_VERSION\"/version = \"$$NEW_VERSION\"/" src-tauri/Cargo.toml; \
echo "$(CYAN)Updating tauri.conf.json...$(RESET)"; \
sed -i '' "s/\"version\": \"$$OLD_VERSION\"/\"version\": \"$$NEW_VERSION\"/" src-tauri/tauri.conf.json; \
echo "$(CYAN)Syncing Cargo.lock...$(RESET)"; \
cd src-tauri && cargo generate-lockfile --quiet; \
echo "$(GREEN)✓ Version bumped from $$OLD_VERSION to $$NEW_VERSION$(RESET)"; \
echo "$(CYAN)Updated files:$(RESET)"; \
echo " - package.json"; \
echo " - src-tauri/Cargo.toml"; \
echo " - src-tauri/Cargo.lock"; \
echo " - src-tauri/tauri.conf.json"
deps: ## Show outdated dependencies
npm outdated || true
cd src-tauri && cargo outdated 2>/dev/null || echo "Install cargo-outdated: cargo install cargo-outdated"
update-deps: ## Update all dependencies
npm update
cd src-tauri && cargo update
## Screenshots
SCREENSHOT_DIR := docs/screenshots
screenshot-setup: ## Install GetWindowID for screenshot capture (macOS)
@which GetWindowID > /dev/null 2>&1 || (echo "$(CYAN)Installing GetWindowID...$(RESET)" && brew install smokris/getwindowid/getwindowid)
screenshot-build: ## Build debug app bundle for screenshots (deep links require debug build)
@echo "$(CYAN)Building debug bundle for screenshots...$(RESET)"
TAURI_SIGNING_PRIVATE_KEY="" npm run tauri:build -- --debug --bundles app --config src-tauri/tauri.conf.debug.json 2>&1 || \
([ -d "src-tauri/target/debug/bundle/macos/Kubeli.app" ] && echo "$(GREEN)✓ Debug bundle created (signing skipped)$(RESET)" || exit 1)
@echo "$(GREEN)✓ Debug bundle ready$(RESET)"
screenshots: screenshot-setup ## Capture screenshots of all views via deep links (debug build)
@if [ ! -d "src-tauri/target/debug/bundle/macos/Kubeli.app" ]; then \
echo "$(YELLOW)Debug bundle not found. Building...$(RESET)"; \
$(MAKE) screenshot-build; \
fi
@./scripts/capture-screenshots.sh $(SCREENSHOT_DIR)
## Code Verification (Vet)
vet: ## Verify all branch changes against main with AI review
@if ! command -v vet >/dev/null 2>&1; then \
echo "$(YELLOW)vet not found. Installing...$(RESET)"; \
$(MAKE) vet-install; \
fi
@BRANCH=$$(git rev-parse --abbrev-ref HEAD); \
BASE=$$(git merge-base main HEAD); \
COMMIT_COUNT=$$(git rev-list --count $$BASE..HEAD); \
if [ -n "$(GOAL)" ]; then \
GOAL="$(GOAL)"; \
else \
GOAL="$$(git log $$BASE..HEAD --pretty=%s | paste -sd ', ' -)"; \
fi; \
echo "$(CYAN)Reviewing branch '$$BRANCH' ($$COMMIT_COUNT commits since main)$(RESET)"; \
echo "$(CYAN)Goal: $$GOAL$(RESET)"; \
echo ""; \
vet "$$GOAL" --base-commit $$BASE --agentic --agent-harness claude
vet-install: ## Install vet CLI for AI code verification
@if command -v vet >/dev/null 2>&1; then \
echo "$(GREEN)✓ vet already installed ($$(vet --version))$(RESET)"; \
else \
echo "$(CYAN)Installing vet (verify-everything)...$(RESET)"; \
if command -v pipx >/dev/null 2>&1; then \
pipx install verify-everything; \
elif command -v uv >/dev/null 2>&1; then \
uv tool install verify-everything; \
elif command -v pip >/dev/null 2>&1; then \
pip install verify-everything; \
else \
echo "$(YELLOW)Error: No Python package manager found (pipx, uv, or pip required)$(RESET)"; \
exit 1; \
fi; \
echo "$(GREEN)✓ vet installed$(RESET)"; \
fi
## Help
help: ## Show this help message
@echo "$(CYAN)Kubeli Development Commands$(RESET)"
@echo ""
@echo "$(YELLOW)Usage:$(RESET) make [target]"
@echo ""
@awk '\
/^## / { printf "\n$(YELLOW)%s$(RESET)\n", substr($$0, 4); next } \
/^[a-zA-Z0-9_-]+:.*## / { \
target = $$0; sub(/:.*/, "", target); \
description = $$0; sub(/^.*## /, "", description); \
printf " $(GREEN)%-28s$(RESET) %s\n", target, description \
}' $(MAKEFILE_LIST)