-
Notifications
You must be signed in to change notification settings - Fork 22
Expand file tree
/
Copy pathMakefile
More file actions
588 lines (486 loc) · 25.8 KB
/
Makefile
File metadata and controls
588 lines (486 loc) · 25.8 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
# Open Edison Makefile
########################################################
# Colors
########################################################
# ANSI color codes
GREEN=\033[0;32m
YELLOW=\033[0;33m
RED=\033[0;31m
BLUE=\033[0;34m
RESET=\033[0m
PYTHON=uv run python
TEST=uv run pytest
PYTEST_ARGS ?=
PROJECT_ROOT=.
PIP=uv pip
.PHONY: help
help: ## Show this help message
@echo "$(BLUE)Available targets:$(RESET)"
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " $(YELLOW)%-20s$(RESET) %s\n", $$1, $$2}'
# Default target
.PHONY: all
all: run ## Run the Open Edison MCP Proxy Server (default)
# Run the Open Edison MCP proxy server
.PHONY: run dev
run: check_uv sync frontend_pack ## Sync deps, build dashboard and run the Open Edison MCP Proxy Server
@echo "🚀 Starting Open Edison MCP Proxy Server..."
uv run open-edison
dev: check_uv sync frontend_pack ## Sync deps, build dashboard and run the Open Edison MCP Proxy Server
@echo "🚀 Starting Open Edison MCP Proxy Server with development config..."
OPEN_EDISON_CONFIG_DIR=$(PROJECT_ROOT)/dev_config_dir uv run open-edison
########################################################
# Check dependencies
########################################################
check_uv: ## Check if uv is installed and show version
@echo "$(YELLOW)🔍Checking uv version...$(RESET)"
@if ! command -v uv > /dev/null 2>&1; then \
echo "$(RED)uv is not installed. Please install uv before proceeding.$(RESET)"; \
exit 1; \
else \
uv --version; \
fi
########################################################
# Python dependency-related
########################################################
update_python_dep: check_uv ## Update and sync Python dependencies
@echo "$(YELLOW)🔄Updating python dependencies...$(RESET)"
@uv sync
sync: check_uv ## Sync Python dependencies
@echo "$(YELLOW)🔄Syncing python dependencies...$(RESET)"
@uv sync
########################################################
# Setup and initialization
########################################################
setup: check_uv sync ## Setup the project for development
@echo "$(YELLOW)🔧 Setting up Open Edison for development...$(RESET)"
@if [ ! -f config.json ]; then \
echo "$(YELLOW)📝 Creating default config.json...$(RESET)"; \
$(PYTHON) -c "from src.config import Config; cfg = Config(); cfg.create_default(); cfg.save()"; \
fi
@echo "$(GREEN)✅ Setup complete! Edit config.json to configure your MCP servers.$(RESET)"
########################################################
# Run Tests
########################################################
TEST_TARGETS = tests/
# Tests
.PHONY: test
test: check_uv ## Run project tests (use PYTEST_ARGS to pass extra flags, e.g. PYTEST_ARGS='-k pattern')
@echo "$(GREEN)🧪Running tests...$(RESET)"
$(TEST) $(PYTEST_ARGS) $(TEST_TARGETS)
@echo "$(GREEN)✅Tests passed.$(RESET)"
# Run a specific test/file by passing node id via T, e.g.:
# make test_one T="tests/test_telemetry_e2e.py::test_real_otlp_export"
.PHONY: test_one
test_one: check_uv ## Run a single test: make test_one T="path::node::id"
@if [ -z "$(T)" ]; then \
echo "$(RED)Provide T=path_or_node_id (e.g. tests/test_foo.py::test_bar)$(RESET)"; \
exit 2; \
fi
@echo "$(GREEN)🧪Running single test $(T)...$(RESET)"
$(TEST) $(T)
.PHONY: test_e2e
test_e2e: check_uv ## Run the real telemetry e2e test (exports metrics)
@echo "$(GREEN)🧪Running telemetry e2e test...$(RESET)"
EDISON_OTEL_E2E=1 $(TEST) tests/test_telemetry_e2e.py::test_real_otlp_export
########################################################
# Linting and Code Quality
########################################################
lint: check_uv ## Lint code with Ruff (src only)
@echo "$(YELLOW)🔍Linting project with Ruff...$(RESET)"
@uv run ruff check .
@echo "$(GREEN)✅Ruff linting completed.$(RESET)"
format: check_uv ## Format code with uv
@echo "$(YELLOW)🎨Formatting code with uv...$(RESET)"
@uv format
@echo "$(GREEN)✅Code formatting completed.$(RESET)"
fix: check_uv ## Auto-fix linting issues with Ruff
@echo "$(YELLOW)🔧Fixing linting issues with Ruff...$(RESET)"
@uv run ruff check . --fix
@echo "$(GREEN)✅Linting fixes applied.$(RESET)"
ty_checker_check: check_uv ## Run type checking with Ty
@echo "$(YELLOW)🔍Running Ty...$(RESET)"
@uv run ty check
@echo "$(GREEN)✅Ty completed.$(RESET)"
deadcode: check_uv ## Find unused code with Vulture (fails on findings)
@echo "$(YELLOW)🪦 Scanning for dead code with Vulture...$(RESET)"
@uv run vulture src tests --min-confidence 60
@echo "$(GREEN)✅Vulture found no unused code (confidence ≥ 60).$(RESET)"
# Verify built distributions contain the Claude Desktop DXT
.PHONY: verify_dxt_in_artifacts
verify_dxt_in_artifacts: desktop_ext build_dist ## Build dists and assert desktop_ext/open-edison-connector.dxt is present
@echo "$(YELLOW)🔎 Verifying DXT presence in wheel and sdist...$(RESET)"
@$(PYTHON) scripts/verify_dxt_in_artifacts.py
@echo "$(GREEN)✅ DXT verified in artifacts.$(RESET)"
ci: sync lint ty_checker_check deadcode test verify_dxt_in_artifacts ## Run CI checks (sync deps, lint, type check, dead code scan, tests, artifact check)
@echo "$(GREEN)✅CI checks completed.$(RESET)"
########################################################
# Version Guard
########################################################
.PHONY: check_higher_than_main
check_higher_than_main: check_uv ## Fail if local pyproject version is not greater than origin/main
@echo "$(YELLOW)🔍 Comparing version with origin/main...$(RESET)"
@uv run python scripts/version_guard.py --base-branch main --file pyproject.toml
@echo "$(GREEN)✅ Version is greater than origin/main.$(RESET)"
########################################################
# Configuration Management
########################################################
config_create: check_uv ## Create a new default config.json
@echo "$(YELLOW)📝Creating default config.json...$(RESET)"
@$(PYTHON) -c "from src.config import Config; cfg = Config(); cfg.create_default(); cfg.save()"
@echo "$(GREEN)✅Default config.json created. Edit it to configure your MCP servers.$(RESET)"
config_validate: check_uv ## Validate the current config.json
@echo "$(YELLOW)🔍Validating config.json...$(RESET)"
@$(PYTHON) -c "from src.config import config; print('✅ Configuration is valid')"
@echo "$(GREEN)✅Configuration validation completed.$(RESET)"
########################################################
# Docker (optional)
########################################################
DOCKER_IMAGE_NAME = open-edison
DOCKER_IMAGE_TAG = $(shell git rev-parse --short HEAD)
docker_build: ## Build the Docker image
@echo "$(YELLOW)🔍Building Docker image...$(RESET)"
@docker build -t $(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG) -t $(DOCKER_IMAGE_NAME):latest .
@echo "$(GREEN)✅Docker image built and tagged as :$(DOCKER_IMAGE_TAG) and :latest.$(RESET)"
docker_run: docker_build ## Run the Docker image
@echo "$(YELLOW)🔍Running Docker image and exposing ports 3000, 3001, and 50001...$(RESET)"
@docker run -it -e OPEN_EDISON_CONFIG_DIR=/app -p 3000:3000 -p 3001:3001 -p 50001:50001 -v $(PWD)/config.json:/app/config.json $(DOCKER_IMAGE_NAME):latest
@echo "$(GREEN)✅Docker image done running. $(RESET)"
# Verify README curl | bash installer works on a clean Ubuntu base image
.PHONY: install_curl_test
install_curl_test: ## Build an Ubuntu-based image that runs the curl installer (smoke test)
@echo "$(YELLOW)🧪 Building installer test image (Ubuntu + curl | bash)...$(RESET)"
@docker build -f installation_test/Dockerfile -t open-edison-install-test:latest .
@echo "$(GREEN)✅ Installer test image built successfully.$(RESET)"
########################################################
# Package for distribution
########################################################
build: check_uv ## Build the package
@echo "$(YELLOW)📦Building package...$(RESET)"
@uv build
@echo "$(GREEN)✅Package built successfully.$(RESET)"
########################################################
# PyPI packaging and publish
########################################################
.PHONY: clean_dist build_dist check_twine publish_testpypi test_publish publish_pypi publish_pre_pypi mark_prerelease mark_release
clean_dist: ## Remove dist/ directory
@echo "$(YELLOW)🧹Cleaning dist directory...$(RESET)"
@rm -rf dist
@echo "$(GREEN)✅dist cleaned.$(RESET)"
build_dist: check_uv clean_dist ## Build source and wheel distributions
@echo "$(YELLOW)📦Building sdist and wheel...$(RESET)"
@uv build
@echo "$(GREEN)✅Distributions built in dist/. $(RESET)"
check_twine: check_uv ## Ensure twine is available
@echo "$(YELLOW)🔍Checking for twine...$(RESET)"
@uv run python -c "import twine, sys; print('twine', twine.__version__)" || (echo "$(RED)twine not found. Run 'uv sync' to install dev deps.$(RESET)"; exit 1)
publish_testpypi: build_package check_twine ## Upload distributions to TestPyPI
@echo "$(YELLOW)🚀Uploading to TestPyPI...$(RESET)"
@echo "$(YELLOW)🔎 Validating metadata with twine check...$(RESET)"
@uv run python -m twine check dist/*
@uv run python -m twine upload --skip-existing --repository testpypi dist/* --verbose
@echo "$(GREEN)✅Upload to TestPyPI complete.$(RESET)"
test_publish: publish_testpypi ## Alias: publish to TestPyPI
publish_pypi: build_package check_twine ## Upload distributions to PyPI (production)
@echo "$(YELLOW)🚀Uploading to PyPI...$(RESET)"
@echo "$(YELLOW)🔎 Validating metadata with twine check...$(RESET)"
@uv run python -m twine check dist/*
@uv run python -m twine upload --repository pypi dist/* --verbose
@echo "$(GREEN)✅Upload to PyPI complete.$(RESET)"
publish_pre_pypi: build_package check_twine ## Upload pre-release distributions to PyPI (requires a/b/rc/dev version)
@echo "$(YELLOW)🚀Uploading pre-release to PyPI...$(RESET)"
@echo "$(YELLOW)🔎 Checking version is a pre-release (aN/bN/rcN/devN)...$(RESET)"
@$(PYTHON) scripts/verify_prerelease_version.py
@echo "$(YELLOW)🔎 Validating metadata with twine check...$(RESET)"
@uv run python -m twine check dist/*
@uv run python -m twine upload --repository pypi dist/* --verbose
@echo "$(GREEN)✅ Pre-release upload to PyPI complete.$(RESET)"
########################################################
# Version Marking (pre-release / release)
########################################################
mark_prerelease: ## Add or bump pre-release suffix (default rc); commits the change
@echo "$(YELLOW)🔧 Marking version as pre-release (rc by default)...$(RESET)"
@$(PYTHON) scripts/version_mark.py prerelease --tag rc --commit
mark_release: ## Strip any pre-release suffix; commits the change
@echo "$(YELLOW)🔧 Marking version as release (strip pre-release)...$(RESET)"
@$(PYTHON) scripts/version_mark.py release --commit
# Aliases for publishing to real PyPI
.PHONY: publish release
publish: publish_pypi ## Alias: publish to PyPI (production)
########################################################
# Desktop Extension
########################################################
.PHONY: desktop_ext desktop_ext_test
desktop_ext: ## Build the desktop extension for Claude Desktop
@echo "$(YELLOW)📦Building Open Edison Desktop Extension...$(RESET)"
@if [ ! -d "desktop_ext" ]; then \
echo "$(RED)❌ desktop_ext directory not found$(RESET)"; \
exit 1; \
fi
@cd desktop_ext && ./build.sh
@if [ -f "desktop_ext/desktop_ext.dxt" ]; then \
cp desktop_ext/desktop_ext.dxt ./open-edison-connector.dxt; \
echo "$(GREEN)✅Desktop extension copied to $(PROJECT_ROOT)/open-edison-connector.dxt$(RESET)"; \
elif [ -f "desktop_ext/open-edison-connector.dxt" ]; then \
cp desktop_ext/open-edison-connector.dxt ./open-edison-connector.dxt; \
echo "$(GREEN)✅Desktop extension copied to $(PROJECT_ROOT)/open-edison-connector.dxt$(RESET)"; \
else \
echo "$(YELLOW)⚠️ Extension file not found, may need DXT CLI installed$(RESET)"; \
echo "$(YELLOW)Available files in desktop_ext:$(RESET)"; \
ls -la desktop_ext/; \
fi
@echo "$(GREEN)✅Desktop extension built successfully.$(RESET)"
@echo "$(BLUE)📋 Install in Claude Desktop by dragging open-edison-connector.dxt to Settings → Extensions$(RESET)"
desktop_ext_test: ## Test the desktop extension configuration
@echo "$(YELLOW)🧪Testing desktop extension configuration...$(RESET)"
@if [ ! -d "desktop_ext" ]; then \
echo "$(RED)❌ desktop_ext directory not found$(RESET)"; \
exit 1; \
fi
@cd desktop_ext && node test_connection.js
@echo "$(GREEN)✅Desktop extension test completed.$(RESET)"
########################################################
# Git Hooks
########################################################
.PHONY: install_git_hooks
install_git_hooks: ## Install project git hooks (pre-push)
@echo "$(YELLOW)🔧 Installing git hooks...$(RESET)"
@mkdir -p .git/hooks
@cp scripts/git-hooks/pre-push .git/hooks/pre-push
@chmod +x .git/hooks/pre-push
@echo "$(GREEN)✅ pre-push hook installed.$(RESET)"
.PHONY: hooks-install
hooks-install: ## Configure repo to use scripts/git-hooks via core.hooksPath
@echo "$(YELLOW)🔧 Configuring git hooks path to scripts/git-hooks...$(RESET)"
@chmod +x scripts/git-hooks/pre-push
@git config --local core.hooksPath scripts/git-hooks
@echo "$(GREEN)✅ core.hooksPath set to scripts/git-hooks; pre-push hook activated.$(RESET)"
########################################################
# Version Management
########################################################
.PHONY: show_version bump_version
# PART can be one of: patch (default), minor, major
PART ?= patch
show_version: ## Show current project version from pyproject.toml
@$(PYTHON) - <<-'PY'
import re, sys
with open('pyproject.toml','r', encoding='utf-8') as f:
text = f.read()
m = re.search(r'^version\s*=\s*"(\d+)\.(\d+)\.(\d+)"\s*$', text, re.MULTILINE)
if not m:
print('Version not found in pyproject.toml', file=sys.stderr)
sys.exit(1)
print(f"Current version: {m.group(1)}.{m.group(2)}.{m.group(3)}")
PY
bump_version: ## Bump project version in pyproject.toml (PART=patch|minor|major; default: patch), sync GUI version, and commit it
@echo "$(YELLOW)🔧 Bumping $(PART) version in pyproject.toml...$(RESET)"
@$(PYTHON) scripts/version_bump.py --part $(PART) --commit
@echo "$(GREEN)✅ Version bumped and committed (GUI version synced automatically).$(RESET)"
bump_version_no_commit: ## Bump version without committing (PART=patch|minor|major)
@echo "$(YELLOW)🔧 Bumping $(PART) version in pyproject.toml (no commit)...$(RESET)"
@$(PYTHON) scripts/version_bump.py --part $(PART) --no-commit
@echo "$(GREEN)✅ Version bumped (not committed, GUI version synced automatically).$(RESET)"
bump_version_amend: ## Bump version and amend the last commit
@echo "$(YELLOW)🔧 Bumping $(PART) version and amending last commit...$(RESET)"
@$(PYTHON) scripts/version_bump.py --part $(PART) --commit --amend
@echo "$(GREEN)✅ Version bumped and amended into last commit (GUI version synced automatically).$(RESET)"
# GUI/Desktop version helpers (keep GUI app version aligned with backend when bundling)
.PHONY: gui_check_version_matches_backend gui_sync_version_to_backend
gui_check_version_matches_backend: ## Fail if GUI app version != backend version
@GUI_VER=$$(node -p "require('./gui/package.json').version"); \
BACKEND_VER=$$(grep -E '^[[:space:]]*version[[:space:]]*=[[:space:]]*"[0-9]+\.[0-9]+\.[0-9]+"' pyproject.toml | head -1 | sed -E 's/.*"([0-9]+\.[0-9]+\.[0-9]+)".*/\1/'); \
if [ "$$GUI_VER" != "$$BACKEND_VER" ]; then \
echo "$(RED)❌ Version mismatch: GUI=$$GUI_VER, backend=$$BACKEND_VER. Bump GUI to match backend.$(RESET)"; \
exit 3; \
else \
echo "$(GREEN)✅ Versions aligned: $$GUI_VER$(RESET)"; \
fi
gui_sync_version_to_backend: ## Set gui/package.json version to backend version from pyproject.toml
@BACKEND_VER=$$(grep -E '^[[:space:]]*version[[:space:]]*=[[:space:]]*"[0-9]+\.[0-9]+\.[0-9]+"' pyproject.toml | head -1 | sed -E 's/.*"([0-9]+\.[0-9]+\.[0-9]+)".*/\1/'); \
if [ -z "$$BACKEND_VER" ]; then \
echo "$(RED)❌ Could not determine backend version from pyproject.toml$(RESET)"; \
exit 2; \
fi; \
node -e "const fs=require('fs');const p=require('./gui/package.json');p.version='$$BACKEND_VER';fs.writeFileSync('./gui/package.json',JSON.stringify(p,null,2)+'\n')"; \
echo "$(GREEN)✅ gui/package.json version set to $$BACKEND_VER$(RESET)"
########################################################
# Frontend Website
########################################################
.PHONY: website website_dev website_build website_install
website_install: ## Install frontend dependencies
@echo "$(YELLOW)📦 Installing frontend deps...$(RESET)"
@cd frontend && npm install
@echo "$(GREEN)✅ Frontend deps installed$(RESET)"
website_dev: ## Run the frontend dev server (opens on port 5174 if 5173 busy)
@echo "$(YELLOW)▶️ Starting frontend dev server...$(RESET)"
@cd frontend && (npm run dev || npm run dev -- --port 5174)
website_build: ## Build the frontend for production
@echo "$(YELLOW)🏗️ Building frontend...$(RESET)"
@cd frontend && npm run build
@echo "$(GREEN)✅ Frontend build complete$(RESET)"
website: website_install website_dev ## Install and run the frontend website
@:
########################################################
# Frontend packaging into src/frontend_dist for runtime
########################################################
.PHONY: frontend_pack
frontend_pack: ## Build the frontend and sync to src/frontend_dist for the server to serve
@echo "$(YELLOW)🏗️ Building frontend (vite) for runtime...$(RESET)"
@cd frontend && npm install && npm run build
@echo "$(YELLOW)📦 Syncing built dashboard to src/frontend_dist...$(RESET)"
@rm -rf src/frontend_dist && mkdir -p src/frontend_dist
@cp -R frontend/dist/* src/frontend_dist/
@echo "$(GREEN)✅ Frontend packed to src/frontend_dist.$(RESET)"
########################################################
# GUI
########################################################
.PHONY: gui gui_dev gui_pack frontend_pack
.PHONY: gui_run gui_run_wizard install-check
install-check: ## Ensure GUI dependencies are installed
@echo "$(YELLOW)🔍 Checking GUI dependencies...$(RESET)"
@if ! command -v node > /dev/null 2>&1; then \
echo "$(RED)❌ Node.js is not installed. Please install Node.js (v18+).$(RESET)"; \
exit 1; \
fi
@if ! command -v npm > /dev/null 2>&1; then \
echo "$(RED)❌ npm is not installed. Please install npm.$(RESET)"; \
exit 1; \
fi
@if [ ! -d "gui/node_modules" ] || [ ! -d "gui/node_modules/vite" ]; then \
echo "$(YELLOW)📦 Installing GUI dependencies...$(RESET)"; \
cd gui && npm install; \
else \
echo "$(GREEN)✅ GUI dependencies present.$(RESET)"; \
fi
gui_dev: install-check frontend_pack gui_pack ## Run the desktop app in development mode
@echo "$(BLUE)🚀 Starting Open Edison Desktop in development mode...$(RESET)"
@cd gui && OPEN_EDISON_GUI_MODE=development npm run dev
gui_run: install-check gui_pack ## Run the desktop app in development mode
@echo "$(BLUE)🚀 Starting Open Edison Desktop...$(RESET)"
@cd gui && npm run electron
gui_run_wizard: install-check gui_pack ## Run the desktop app in development mode
@echo "$(BLUE)🚀 Starting Open Edison Desktop with wizard...$(RESET)"
@cd gui && npm run first-install
gui_pack: install-check sync_gui_version ## Build the desktop app for distribution
@echo "$(YELLOW)🏗️ Building desktop app (Electron) for distribution...$(RESET)"
@cd gui && npm install && npm run build
@echo "$(YELLOW)📦 Building Electron distribution packages...$(RESET)"
@cd gui && CSC_IDENTITY_AUTO_DISCOVERY=false APPLE_ID="" npm run dist
@echo "$(GREEN)✅ Desktop app packaged to gui/release/.$(RESET)"
sync_gui_version: ## Sync version from pyproject.toml to gui/package.json
@echo "$(YELLOW)🔄 Syncing version from pyproject.toml to gui/package.json...$(RESET)"
@$(PYTHON) scripts/sync_gui_version.py
########################################################
# PyInstaller (freeze backend) and Electron dist
########################################################
.PHONY: pyinstall electron_dist app_dist
pyinstall: check_uv frontend_pack ## Build standalone backend binary with PyInstaller (depends on frontend_pack)
@echo "$(YELLOW)🏗️ Building PyInstaller binary...$(RESET)"
@uv run pyinstaller -F -n open-edison-backend -p src \
--additional-hooks-dir pyinstaller_hooks \
--add-data "src/frontend_dist:src/frontend_dist" \
--collect-submodules uvicorn \
--hidden-import=sqlite3 \
--hidden-import=_sqlite3 \
--hidden-import=pysqlite2 \
--hidden-import=MySQLdb \
--collect-submodules sqlalchemy.dialects \
main.py
@echo "$(GREEN)✅ PyInstaller binary at dist/open-edison-backend$(RESET)"
@echo "$(YELLOW)🏗️ Building Setup Wizard API binary...$(RESET)"
@uv run pyinstaller -F -n open-edison-wizard -p src \
--additional-hooks-dir pyinstaller_hooks \
--collect-submodules uvicorn \
src/mcp_importer/wizard_server.py
@echo "$(GREEN)✅ PyInstaller binary at dist/open-edison-wizard$(RESET)"
.PHONY: pyinstall_clean
pyinstall_clean: ## Clean PyInstaller outputs (binary, build dir, spec, copied backend)
@echo "$(YELLOW)🧹 Cleaning PyInstaller artifacts...$(RESET)"
@rm -rf build
@rm -f open-edison-backend.spec
@rm -f open-edison-wizard.spec
@rm -f dist/open-edison-backend
@rm -f dist/open-edison-wizard
@echo "$(GREEN)✅ PyInstaller artifacts cleaned.$(RESET)"
electron_dist: sync_gui_version ## Build Electron distribution (requires pyinstall run to copy backend)
@echo "$(YELLOW)📦 Preparing Electron app with bundled backend...$(RESET)"
@if [ ! -f "dist/open-edison-backend" ]; then \
echo "$(RED)❌ dist/open-edison-backend not found. Run 'make pyinstall' first.$(RESET)"; \
exit 1; \
fi
@if [ ! -f "dist/open-edison-wizard" ]; then \
echo "$(RED)❌ dist/open-edison-wizard not found. Run 'make pyinstall' first.$(RESET)"; \
exit 1; \
fi
@mkdir -p gui/extraResources/backend
@cp dist/open-edison-backend gui/extraResources/backend/
@cp dist/open-edison-wizard gui/extraResources/backend/
@echo "$(YELLOW)🏗️ Building Electron app (no signing/notarization)...$(RESET)"
@cd gui && CSC_IDENTITY_AUTO_DISCOVERY=false APPLE_ID="" npm run dist
@echo "$(GREEN)✅ Electron app built to gui/release/$(RESET)"
.PHONY: electron_dist_env notarize_dmg
# Load environment variables from .env and build with signing/notarization
electron_dist_env: sync_gui_version ## Load .env then build Electron distribution (sign/notarize if configured)
@echo "$(YELLOW)📦 Preparing Electron app with bundled backend (with .env)...$(RESET)"
@if [ ! -f "dist/open-edison-backend" ]; then \
echo "$(RED)❌ dist/open-edison-backend not found. Run 'make pyinstall' first.$(RESET)"; \
exit 1; \
fi
@if [ ! -f "dist/open-edison-wizard" ]; then \
echo "$(RED)❌ dist/open-edison-wizard not found. Run 'make pyinstall' first.$(RESET)"; \
exit 1; \
fi
@mkdir -p gui/extraResources/backend
@cp dist/open-edison-backend gui/extraResources/backend/
@cp dist/open-edison-wizard gui/extraResources/backend/
@echo "$(YELLOW)🏗️ Building Electron app (sign/notarize from .env)...$(RESET)"
@set -a; [ -f ".env" ] && . ./.env; set +a; cd gui && npm run dist
@echo "$(GREEN)✅ Electron app built to gui/release/$(RESET)"
electron_dist_clean: pyinstall_clean ## Clean Electron distribution outputs
@echo "$(YELLOW)🧹 Cleaning Electron distribution artifacts...$(RESET)"
@rm -rf gui/extraResources/backend
@rm -rf gui/release
@rm -rf gui/dist
@echo "$(GREEN)✅ Electron distribution artifacts cleaned.$(RESET)"
app_dist: pyinstall electron_dist ## Build full macOS app bundle (backend + frontend + GUI)
@echo "$(GREEN)✅ Full macOS app bundle built with version from pyproject.toml$(RESET)"
# Publish packaged GUI to GitHub Releases using electron-builder (requires GH_TOKEN)
# These releases are checked by Open Edison Desktop for auto-updates
.PHONY: app_release
app_release: app_dist ## Build (pyinstaller + electron) and publish GUI to GitHub Releases
@if [ -z "$$GH_TOKEN" ]; then \
echo "$(RED)❌ GH_TOKEN is not set. Export GH_TOKEN with a GitHub token that has repo permissions.$(RESET)"; \
exit 2; \
fi
@echo "$(YELLOW)🔎 Checking if release for current GUI version already exists...$(RESET)"
@VERSION=$$(node -p "require('./gui/package.json').version"); \
TAG=v$$VERSION; \
STATUS=$$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: token $$GH_TOKEN" https://api.github.com/repos/Edison-Watch/open-edison/releases/tags/$$TAG); \
if [ "$$STATUS" = "200" ]; then \
echo "$(RED)❌ GitHub release $$TAG already exists for Edison-Watch/open-edison. Bump version before releasing.$(RESET)"; \
exit 3; \
fi
@echo "$(YELLOW)🚀 Publishing GUI to GitHub Releases (Edison-Watch/open-edison)...$(RESET)"
@cd gui && npm run build && npx electron-builder --publish always
@echo "$(GREEN)✅ GUI release published. Check GitHub Releases for artifacts.$(RESET)"
########################################################
# Package Build (Python wheel + packaged frontend)
########################################################
.PHONY: build_package
build_package: check_uv clean_dist ## Build frontend, desktop extension, then build Python wheel
@echo "$(YELLOW)🏗️ Building frontend (vite) and packaging Python wheel...$(RESET)"
@cd frontend && npm install && npm run build
@echo "$(YELLOW)📦 Syncing built dashboard to src/frontend_dist...$(RESET)"
@rm -rf src/frontend_dist && mkdir -p src/frontend_dist
@cp -R frontend/dist/* src/frontend_dist/
@echo "$(YELLOW)📦 Building desktop extension DXT...$(RESET)"
@$(MAKE) desktop_ext
@echo "$(YELLOW)📦 Building Python wheel...$(RESET)"
@uv build
@echo "$(GREEN)✅ build_package complete. Wheel contains packaged dashboard (frontend_dist).$(RESET)"
########################################################
# Ngrok Tunnel Management
########################################################
.PHONY: ngrok-start ngrok-stop ngrok-status ngrok-logs
ngrok-start: ## Start ngrok tunnel for Open Edison
@echo "$(BLUE)🚀 Starting ngrok tunnel for Open Edison...$(RESET)"
@ngrok start --config=ngrok.yml open-edison-mcp