Repository navigation
Expand file tree
/
Copy pathMakefile
More file actions
241 lines (200 loc) · 12.8 KB
/
Copy pathMakefile
File metadata and controls
241 lines (200 loc) · 12.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
.PHONY: help dev dev-backend dev-frontend dev-unix stop test lint format migrate seed seed-regional seed-cwicr-v3 build qa-screenshots
# ─── Variables ──────────────────────────────────────────────────────────────
BACKEND_DIR = backend
FRONTEND_DIR = frontend
DOCKER_COMPOSE = docker compose
# ─── Help ───────────────────────────────────────────────────────────────────
help: ## Show this help
@echo ""
@echo " OpenConstructionERP — Construction Cost Estimation Platform"
@echo " ─────────────────────────────────────────────────────────────"
@echo ""
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | \
awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}'
@echo ""
@echo " Quick start: make quickstart → http://localhost:8080"
@echo " Local dev: make setup && make dev → http://localhost:5173"
@echo ""
# ─── Development ────────────────────────────────────────────────────────────
infra: ## Start infrastructure (PostgreSQL, Redis, MinIO)
$(DOCKER_COMPOSE) up -d postgres redis minio
infra-ai: ## Start infrastructure + AI services (Qdrant)
$(DOCKER_COMPOSE) --profile ai up -d
stop: ## Stop all services
$(DOCKER_COMPOSE) --profile ai down
dev-backend: infra ## Start backend dev server
cd $(BACKEND_DIR) && uvicorn app.main:create_app --factory --reload --port 8000
dev-frontend: ## Start frontend dev server
cd $(FRONTEND_DIR) && npm run dev
dev: ## Show how to start dev (cross-platform: two terminals)
@echo "OpenConstructionERP local dev needs two terminals (cross-platform safe):"
@echo " Terminal 1: make dev-backend (http://localhost:8000)"
@echo " Terminal 2: make dev-frontend (http://localhost:5173)"
@echo ""
@echo "Linux/macOS users may also try the POSIX-only shortcut: make dev-unix"
# POSIX-only: backgrounds the backend with `&` (bash job control).
# Does NOT work in Windows cmd.exe or MSYS2 make — use `make dev-backend`
# and `make dev-frontend` in two terminals instead.
dev-unix: infra ## Start backend (background) + frontend — POSIX shells only
@echo "Starting OpenConstructionERP (POSIX dev)..."
@echo " Backend: http://localhost:8000"
@echo " Frontend: http://localhost:5173"
@echo ""
@cd $(BACKEND_DIR) && uvicorn app.main:create_app --factory --reload --port 8000 &
@cd $(FRONTEND_DIR) && npm run dev
# ─── Testing ────────────────────────────────────────────────────────────────
test: test-backend test-frontend ## Run all tests
test-backend: ## Run backend tests
cd $(BACKEND_DIR) && pytest -x -v
test-backend-cov: ## Run backend tests with coverage
cd $(BACKEND_DIR) && pytest --cov=app --cov-report=term --cov-report=html
test-frontend: ## Run frontend tests
cd $(FRONTEND_DIR) && npm run test
test-unit: ## Run only unit tests (no DB required)
cd $(BACKEND_DIR) && pytest -x -v -m unit
test-integration: ## Run integration tests (requires DB)
cd $(BACKEND_DIR) && pytest -x -v -m integration
qa-screenshots: ## Capture full-app route grid → qa-report/screenshots/<date>/ (needs backend on :8000 + frontend on :5180)
# NODE_PATH lets a Playwright config sitting outside frontend/ still
# resolve @playwright/test from frontend/node_modules — required when
# running from a worktree that doesn't have its own node_modules.
cd $(FRONTEND_DIR) && NODE_PATH="$(CURDIR)/$(FRONTEND_DIR)/node_modules" npx playwright test --config ../qa/screenshots/playwright.config.ts --reporter=line
# ─── Code Quality ───────────────────────────────────────────────────────────
lint: ## Lint all code
cd $(BACKEND_DIR) && ruff check app/ tests/
cd $(FRONTEND_DIR) && npm run lint
format: ## Format the backend code (the frontend has no automatic formatter)
cd $(BACKEND_DIR) && ruff format app/ tests/
# The frontend half runs the build rather than `npm run typecheck`. Both start
# from the same compiler, but `npm run typecheck` is `tsc --noEmit` and stops
# there, while `npm run build` is `tsc -b` followed by `vite build`, so it also
# resolves the module graph the way the bundle does and `tsc -b` answers from
# .tsbuildinfo where a cold --noEmit does not. `npm run build` is the gate this
# project treats as authoritative, and a green `tsc --noEmit` read as a green
# build is how three broken HEADs shipped recently. It costs a bundle write
# into frontend/dist, which is the price of checking the thing that ships.
typecheck: ## Run type checking
cd $(BACKEND_DIR) && mypy app/
cd $(FRONTEND_DIR) && npm run build
# ─── Database ───────────────────────────────────────────────────────────────
migrate: ## Run all pending migrations
cd $(BACKEND_DIR) && alembic upgrade head
migrate-new: ## Create new migration (usage: make migrate-new MSG="add users table")
cd $(BACKEND_DIR) && alembic revision --autogenerate -m "$(MSG)"
migrate-down: ## Rollback last migration
cd $(BACKEND_DIR) && alembic downgrade -1
seed: ## Load seed data (cost catalog + regional indices). Demo projects are auto-created on first backend startup.
cd $(BACKEND_DIR) && python -m app.scripts.seed_catalog
cd $(BACKEND_DIR) && python -m app.scripts.seed_regional_indices
seed-regional: ## Load regional cost-index seed (v3.12.0, idempotent)
cd $(BACKEND_DIR) && python -m app.scripts.seed_regional_indices
# Override with REGIONS="--regions USA_USD,GB_LONDON" or REGIONS="--top-n 5".
# Default ``--top-n 3`` installs the 3 most-popular catalogues so a fresh
# deployment boots with US/UK/DE rate books pre-loaded — operators no
# longer need to visit /costs and click Install for every region.
REGIONS ?= --top-n 3
seed-cwicr-v3: ## Install CWICR v3 catalogues (REGIONS="--top-n 3" by default; needs CWICR_QDRANT_URL)
cd $(BACKEND_DIR) && python -m scripts.seed_cwicr_v3 $(REGIONS)
db-reset: ## Drop and recreate database (DESTRUCTIVE)
$(DOCKER_COMPOSE) exec postgres psql -U oe -c "DROP DATABASE IF EXISTS openestimate;"
$(DOCKER_COMPOSE) exec postgres psql -U oe -c "CREATE DATABASE openestimate;"
$(MAKE) migrate
$(MAKE) seed
# ─── Module Development ────────────────────────────────────────────────────
module-new: ## Create new module (usage: make module-new NAME=oe_tendering)
cd $(BACKEND_DIR) && python -m app.scripts.scaffold_module $(NAME)
module-test: ## Test specific module (usage: make module-test NAME=oe_boq)
cd $(BACKEND_DIR) && pytest -x -v tests/ -k "$(NAME)"
# ─── Setup (first time) ──────────────────────────────────────────────────
setup: ## First-time setup: install backend + frontend dependencies
@echo "Installing backend dependencies..."
cd $(BACKEND_DIR) && pip install -e .[server]
@echo ""
@echo "Installing frontend dependencies..."
cd $(FRONTEND_DIR) && npm install
@echo ""
@echo "Setup complete! Run 'make dev' to start the application."
@echo " Backend: http://localhost:8000 (FastAPI + PostgreSQL)"
@echo " Frontend: http://localhost:5173 (Vite dev server)"
# ─── Quickstart (single command) ──────────────────────────────────────────
# The two secrets the quickstart stack will not start without, checked here
# rather than left to compose. Compose does say which variable is missing, and
# says it well, but it says it by exiting nonzero - and `quickstart` below turns
# every nonzero exit into "the local build did not finish" and points at
# `quickstart-image`. On a fresh clone with no .env that is the wrong sentence
# twice over: nothing was built, and the remedy loads this same compose file, so
# it stops on the identical `${POSTGRES_PASSWORD:?}` a moment later. The
# stranger this path exists for got a wrong diagnosis followed by a second
# failure that looked unrelated to the first.
#
# Both entry points depend on this, because both read docker-compose.quickstart.yml.
#
# The .env is looked up at $(CURDIR), not as a relative path. Compose resolves it
# beside the compose file, and a recipe resolves a bare `.env` against whatever
# directory make was called from, so `make -C /path/to/repo quickstart` would
# have had this guard report two missing secrets that are sitting right there.
quickstart-secrets:
@if [ -n "$$POSTGRES_PASSWORD" ] && [ -n "$$JWT_SECRET" ]; then exit 0; fi; \
if [ -f "$(CURDIR)/.env" ] \
&& grep -q '^POSTGRES_PASSWORD=..*' "$(CURDIR)/.env" \
&& grep -q '^JWT_SECRET=..*' "$(CURDIR)/.env"; then exit 0; fi; \
echo ""; \
echo "Nothing has started yet. The quickstart stack needs two secrets and"; \
echo "ships no defaults for them, so that no two installations share a"; \
echo "database password or a key that signs their sessions."; \
echo ""; \
echo "Write them once, into a .env beside this Makefile:"; \
echo ""; \
echo " echo \"POSTGRES_PASSWORD=\$$(openssl rand -base64 24)\" > .env"; \
echo " echo \"JWT_SECRET=\$$(openssl rand -hex 32)\" >> .env"; \
echo ""; \
echo "Then run this command again. On Windows PowerShell, where there is no"; \
echo "openssl and no make, README.md has the two-line equivalent."; \
echo ""; \
exit 1
# Building is optional. A failed build used to end at a raw compose error,
# which left people stuck on a step they never had to run, so the failure
# path now names the published image instead.
quickstart: quickstart-secrets ## Start OpenConstructionERP (PostgreSQL + App) — needs a .env with two secrets
@$(DOCKER_COMPOSE) -f docker-compose.quickstart.yml up --build || $(MAKE) --no-print-directory quickstart-build-failed
quickstart-build-failed:
@echo ""
@echo "The local build did not finish. You do not have to build it:"
@echo ""
@echo " make quickstart-image the same stack, from the image we publish"
@echo ""
@echo "If you would rather we fixed the build, please open an issue with the"
@echo "output above and we will take a look."
@exit 1
# Same stack, but the app comes from the image we publish on every release
# instead of building the frontend here. `pull` is spelled out rather than left
# to `up`, so which artefact runs is stated by the command and not inferred from
# compose's build-versus-pull rules. See docker-compose.quickstart.image.yml.
QUICKSTART_IMAGE_FILES = -f docker-compose.quickstart.yml -f docker-compose.quickstart.image.yml
quickstart-image: quickstart-secrets ## Start quickstart from the published image (no local build)
$(DOCKER_COMPOSE) $(QUICKSTART_IMAGE_FILES) pull app
$(DOCKER_COMPOSE) $(QUICKSTART_IMAGE_FILES) up -d
@echo " OpenConstructionERP: http://localhost:8080"
quickstart-image-down: ## Stop the published-image quickstart
$(DOCKER_COMPOSE) $(QUICKSTART_IMAGE_FILES) down
quickstart-down: ## Stop quickstart
$(DOCKER_COMPOSE) -f docker-compose.quickstart.yml down
quickstart-reset: ## Reset quickstart (delete data)
$(DOCKER_COMPOSE) -f docker-compose.quickstart.yml down -v
# ─── Build & Deploy ────────────────────────────────────────────────────────
build: ## Build all Docker images
docker build -t openestimate:latest -f deploy/docker/Dockerfile.unified .
docker build -t openestimate-backend:latest --target api -f deploy/docker/Dockerfile.unified .
docker build -t openestimate-frontend:latest -f deploy/docker/Dockerfile.frontend .
build-unified: ## Build single all-in-one Docker image
docker build -t openestimate:latest -f deploy/docker/Dockerfile.unified .
build-wheel: ## Build Python wheel (pip installable)
cd $(FRONTEND_DIR) && npm ci && npm run build
cd $(BACKEND_DIR) && pip install build && python -m build
# ─── Utilities ──────────────────────────────────────────────────────────────
clean: ## Clean build artifacts
find . -type d -name __pycache__ -exec rm -rf {} +
find . -type d -name .pytest_cache -exec rm -rf {} +
find . -type d -name .ruff_cache -exec rm -rf {} +
rm -rf $(BACKEND_DIR)/htmlcov
rm -rf $(FRONTEND_DIR)/dist