-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathMakefile
More file actions
380 lines (310 loc) · 16.1 KB
/
Copy pathMakefile
File metadata and controls
380 lines (310 loc) · 16.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
# maru — local-first AI workspace desktop app
#
# Tauri 2 + Rust + React 19 + TypeScript. This Makefile bundles the
# common dev / build / test / verify workflows so you don't have to
# remember which lives in pnpm scripts vs cargo.
#
# Quick start:
# make install # one-time setup (pnpm install + generated platform icons)
# make dev # browser dev (mocked Tauri)
# make tauri-dev # native dev shell
# make verify # what CI runs: typecheck + i18n lint + ts test + rust test + build
SHELL := /bin/bash
.DEFAULT_GOAL := help
PNPM ?= pnpm
CARGO ?= cargo
NODE ?= node
TAURI_DIR := src-tauri
ICON_PATH := $(TAURI_DIR)/icons/icon.png
BRAND_ICON_SOURCES := $(wildcard src/assets/brand/*.svg) src/assets/brand/icon-manifest.json scripts/generate-icons.mjs
BENCH_WORKSPACE ?= $(HOME)/workspace/work
CLI_INSTALL_DIR ?= $(HOME)/.local/bin
CLI_BIN_NAME ?= maru
CLI_RELEASE_BIN := $(TAURI_DIR)/target/release/maru-cli
CLI_DEBUG_BIN := $(TAURI_DIR)/target/debug/maru-cli
CLI_INSTALL_BIN := $(CLI_INSTALL_DIR)/$(CLI_BIN_NAME)
CLI_SMOKE_HOME ?= .context/cli-smoke-home
HOMEBREW_TAP_DIR ?= ../homebrew-cask
VERSION ?= $(shell $(NODE) -p "JSON.parse(require('fs').readFileSync('package.json', 'utf8')).version")
RELEASE_TAG ?= v$(VERSION)
MACOS_RELEASE_REPO ?= STAIxBWLB/maru
TAURI_SIGNING_PRIVATE_KEY_FILE ?= $(HOME)/.tauri/maru-updater.key
TAURI_SIGNING_PRIVATE_KEY_PASSWORD_FILE ?= $(HOME)/.tauri/maru-updater.key.password
# ---------------------------------------------------------------------------
# Help (default target)
# ---------------------------------------------------------------------------
.PHONY: help
help: ## Show this help
@printf "maru — make targets\n\n"
@awk 'BEGIN {FS = ":.*##"; printf " \033[36m%-24s\033[0m %s\n", "target", "description"; \
printf " %-24s %s\n", "------", "-----------"} \
/^[a-zA-Z0-9_-]+:.*##/ { printf " \033[36m%-24s\033[0m %s\n", $$1, $$2 }' $(MAKEFILE_LIST)
# ---------------------------------------------------------------------------
# Setup
# ---------------------------------------------------------------------------
.PHONY: install
install: node_modules $(ICON_PATH) ## Install pnpm deps + ensure generated platform icons
node_modules: package.json pnpm-lock.yaml
$(PNPM) install
@touch node_modules
$(ICON_PATH): $(BRAND_ICON_SOURCES) ## Generate the canonical Maru icon set when sources change
$(PNPM) icons:generate
.PHONY: icons
icons: node_modules ## Regenerate web, desktop, iOS, and Android icon assets
$(PNPM) icons:generate
.PHONY: icons-check
icons-check: node_modules ## Fail when generated icon assets are missing or stale
$(PNPM) icons:check
# ---------------------------------------------------------------------------
# Dev
# ---------------------------------------------------------------------------
.PHONY: dev
dev: node_modules ## Start vite dev server (mocked Tauri, browser only)
$(PNPM) dev
.PHONY: tauri-dev
tauri-dev: install ## Start native Tauri dev shell (Rust + React)
$(PNPM) tauri:dev
# ---------------------------------------------------------------------------
# Build
# ---------------------------------------------------------------------------
.PHONY: build
build: node_modules ## Frontend production build (vite)
$(PNPM) build
.PHONY: build-frontend
build-frontend: node_modules ## Frontend bundle after a separate typecheck
$(PNPM) build:frontend
.PHONY: tauri-build
tauri-build: install ## Native Tauri production build (cargo + bundle)
@set -euo pipefail; \
if [ -z "$${TAURI_SIGNING_PRIVATE_KEY:-}" ]; then \
if [ ! -f "$(TAURI_SIGNING_PRIVATE_KEY_FILE)" ]; then \
printf "error: TAURI_SIGNING_PRIVATE_KEY is unset and %s is missing\n" "$(TAURI_SIGNING_PRIVATE_KEY_FILE)" >&2; \
printf "restore the updater private key or export TAURI_SIGNING_PRIVATE_KEY before running make tauri-build\n" >&2; \
exit 1; \
fi; \
export TAURI_SIGNING_PRIVATE_KEY="$$(cat "$(TAURI_SIGNING_PRIVATE_KEY_FILE)")"; \
fi; \
if [ -z "$${TAURI_SIGNING_PRIVATE_KEY_PASSWORD:-}" ] && [ -f "$(TAURI_SIGNING_PRIVATE_KEY_PASSWORD_FILE)" ]; then \
export TAURI_SIGNING_PRIVATE_KEY_PASSWORD="$$(cat "$(TAURI_SIGNING_PRIVATE_KEY_PASSWORD_FILE)")"; \
fi; \
$(PNPM) tauri:build
.PHONY: cli-build
cli-build: $(ICON_PATH) ## Build standalone Maru CLI
cd $(TAURI_DIR) && $(CARGO) build --release -p maru-cli --bin maru-cli
.PHONY: cli-install
cli-install: cli-build ## Install standalone Maru CLI to CLI_INSTALL_DIR (default: ~/.local/bin)
mkdir -p "$(CLI_INSTALL_DIR)"
install -m 0755 "$(CLI_RELEASE_BIN)" "$(CLI_INSTALL_BIN)"
"$(CLI_INSTALL_BIN)" --version
.PHONY: cli-uninstall
cli-uninstall: ## Remove standalone Maru CLI from CLI_INSTALL_DIR
rm -f "$(CLI_INSTALL_BIN)"
.PHONY: cli-version
cli-version: ## Show installed Maru CLI path and version
@set -euo pipefail; \
bin="$$(command -v "$(CLI_BIN_NAME)" || true)"; \
if [ -z "$$bin" ]; then \
printf "error: %s not found in PATH\n" "$(CLI_BIN_NAME)" >&2; \
exit 1; \
fi; \
printf "%s\n" "$$bin"; \
"$$bin" --version
.PHONY: cli-smoke
cli-smoke: cli-build ## Smoke test standalone Maru CLI with an isolated HOME under .context/
@set -euo pipefail; \
rm -rf "$(CLI_SMOKE_HOME)"; \
mkdir -p "$(CLI_SMOKE_HOME)"; \
smoke_home="$$(cd "$(CLI_SMOKE_HOME)" && pwd)"; \
HOME="$$smoke_home" "$(CLI_RELEASE_BIN)" --version; \
HOME="$$smoke_home" "$(CLI_RELEASE_BIN)" doctor --quiet; \
HOME="$$smoke_home" "$(CLI_RELEASE_BIN)" skills dirty --json
.PHONY: cli-smoke-debug
cli-smoke-debug: $(ICON_PATH) ## Smoke a debug CLI after test compilation, avoiding a second release build in CI
cd $(TAURI_DIR) && $(CARGO) build -p maru-cli --bin maru-cli
@set -euo pipefail; \
rm -rf "$(CLI_SMOKE_HOME)"; \
mkdir -p "$(CLI_SMOKE_HOME)"; \
smoke_home="$$(cd "$(CLI_SMOKE_HOME)" && pwd)"; \
HOME="$$smoke_home" "$(CLI_DEBUG_BIN)" --version; \
HOME="$$smoke_home" "$(CLI_DEBUG_BIN)" doctor --quiet; \
HOME="$$smoke_home" "$(CLI_DEBUG_BIN)" skills dirty --json
# ---------------------------------------------------------------------------
# Test / quality
# ---------------------------------------------------------------------------
.PHONY: typecheck
typecheck: node_modules ## tsc --build (no emit)
$(PNPM) typecheck
.PHONY: lint
lint: node_modules ## ESLint gate: hook-dependency + unused-symbol correctness rules (src/ + e2e/)
$(PNPM) lint
.PHONY: test
test: test-ts test-rust ## Run all unit tests (TS vitest + Rust cargo test)
.PHONY: lint-i18n
lint-i18n: ## i18n lint: ko/en key parity + hardcoded UI string scan
$(NODE) scripts/lint-i18n.mjs
.PHONY: check-select-chrome
check-select-chrome: ## Static guard: select rules must not wipe the base chevron via background shorthand
$(NODE) scripts/check-select-chrome.mjs
.PHONY: check-native-e2e-isolation
check-native-e2e-isolation: ## Static guard: no native-e2e runner affordances in the production bundle or Cargo manifest (D-10)
$(NODE) scripts/check-native-e2e-isolation.mjs
# The type scale is the single source of truth (PR #137). A raw px font-size in
# styles.css silently opts that rule out of any future --type-* retune, so the
# pane drifts away from the rest of the app. graph.css/diagram.css still carry
# pre-existing raw values and are not gated yet.
.PHONY: check-type-tokens
check-type-tokens: ## Static guard: styles.css font sizes must use the --type-*/--read-* scale
@! grep -nE 'font-size: *[0-9.]+px' src/styles.css \
|| (echo "check-type-tokens: raw px font-size above — use a --type-*/--read-* token (src/foundations.css)"; exit 1)
.PHONY: test-ts
test-ts: node_modules ## TypeScript / React unit tests (vitest)
$(PNPM) test
.PHONY: test-rust
test-rust: $(ICON_PATH) ## Rust unit + integration tests (cargo test --lib)
cd $(TAURI_DIR) && $(CARGO) test --lib
.PHONY: fmt-check
fmt-check: ## Rust format check (no changes written)
cd $(TAURI_DIR) && $(CARGO) fmt --check
.PHONY: clippy
clippy: $(ICON_PATH) ## Rust lint gate (cargo clippy -D warnings, lib scope)
cd $(TAURI_DIR) && $(CARGO) clippy -- -D warnings
.PHONY: test-cli
test-cli: $(ICON_PATH) ## Compile and test standalone Maru CLI binary
cd $(TAURI_DIR) && $(CARGO) test -p maru-cli --bin maru-cli
.PHONY: test-e2e
test-e2e: node_modules ## Playwright e2e (requires browsers; run `pnpm playwright install` first)
$(PNPM) test:e2e
# Deliberately NOT part of `verify`: it builds and launches a real macOS
# .app under WebDriver and cannot be hermetic (TEST-01, D-15) - the same
# reason verify-integration sits outside verify. It IS part of
# `release-preflight` (D-03, wired in 06-05), which is human-run on macOS.
# This target's frontend build is VITE_NATIVE_E2E=1
# and is deliberately not shippable - re-run `pnpm build:frontend` before
# inspecting a production artifact.
.PHONY: test-e2e-native
test-e2e-native: node_modules ## Native WebDriver e2e against the real app (macOS; builds with the native-e2e feature)
$(PNPM) build:frontend:native-e2e
@# The native-e2e feature enables tauri/custom-protocol, so dist/ is
@# embedded at compile time - but cargo does not track dist/ as a build
@# input and will happily reuse a stale binary (observed: 0.89s no-op
@# build against a dist rebuilt seconds earlier, webview then stuck on
@# about:blank). Touching build.rs forces the maru crate to recompile
@# and re-embed the fresh frontend.
touch $(TAURI_DIR)/build.rs
cd $(TAURI_DIR) && $(CARGO) build --features native-e2e
$(PNPM) test:e2e:native
.PHONY: bench-scan
bench-scan: $(ICON_PATH) ## Bench workspace scan (default: ~/workspace/work; override BENCH_WORKSPACE=/path)
cd $(TAURI_DIR) && MARU_BENCH_WORKSPACE=$(BENCH_WORKSPACE) \
$(CARGO) test --release bench_scan_real_workspace -- --ignored --nocapture --test-threads=1
# Deliberately NOT part of `verify`: this depends on which AI CLIs are installed
# and whether their tokens are live, and a merge gate that fails on an expired
# token is a gate people learn to bypass. Run it when touching provider.rs,
# skill_host/dispatch.rs, agent_host/status.rs or terminal/mod.rs.
.PHONY: verify-integration
verify-integration: $(ICON_PATH) ## Smoke the real installed AI CLIs (availability, auth, usage, permission parsing). Uninstalled CLIs are skipped. MARU_CLI_SMOKE_ROUNDTRIP=1 adds one live prompt per authenticated backend.
cd $(TAURI_DIR) && MARU_CLI_SMOKE=1 \
$(CARGO) test --lib cli_backends_real_smoke -- --ignored --nocapture --test-threads=1
# ---------------------------------------------------------------------------
# Skills / release management
# ---------------------------------------------------------------------------
.PHONY: skills-doctor
skills-doctor: ## Run Maru skills doctor in quiet mode
$(CARGO) run --manifest-path $(TAURI_DIR)/Cargo.toml -p maru-cli --bin maru-cli -- doctor --quiet
.PHONY: skills-doctor-json
skills-doctor-json: ## Run Maru skills doctor and print JSON
$(CARGO) run --manifest-path $(TAURI_DIR)/Cargo.toml -p maru-cli --bin maru-cli -- doctor --json
.PHONY: skills-dirty
skills-dirty: ## List dirty Maru skills as JSON
$(CARGO) run --manifest-path $(TAURI_DIR)/Cargo.toml -p maru-cli --bin maru-cli -- skills dirty --json
.PHONY: skills-bootstrap-verify
skills-bootstrap-verify: ## Validate the embedded bootstrap snapshot against the OTA bundle schema
$(NODE) scripts/skills-bootstrap-refresh.mjs verify
.PHONY: skills-bootstrap-refresh
skills-bootstrap-refresh: ## Replace src-tauri/skills-bootstrap with the newest OTA bundle
$(NODE) scripts/skills-bootstrap-refresh.mjs refresh
.PHONY: diff-check
diff-check: ## Check working tree diff for whitespace errors
git diff --check
.PHONY: release-version-check
release-version-check: ## Check every release version surface and optional RELEASE_TAG
$(NODE) scripts/check-release-version.mjs $(if $(RELEASE_TAG),--tag "$(RELEASE_TAG)")
.PHONY: release-checks
release-checks: verify test-cli cli-smoke-debug ## Full verify plus release-only CLI and debug Tauri checks
$(PNPM) tauri build --debug --no-bundle --config '{"build":{"beforeBuildCommand":null}}'
@# D-10 artifact-level scan: the unstripped debug binary carries the same
@# default feature set the release build uses, so a native-e2e plugin that
@# arrived through a stray flag or a dependency is caught here. Must run
@# BEFORE the debug-artifact prune below, which deletes the binary.
$(NODE) scripts/check-native-e2e-isolation.mjs --binary $(TAURI_DIR)/target/debug/maru
$(PNPM) clean:tauri-debug -- --force
.PHONY: release-preflight-core
release-preflight-core: ## Release preflight core: diff, verify, and debug no-bundle Tauri build
$(MAKE) diff-check
$(MAKE) release-checks
.PHONY: release-preflight
release-preflight: ## Complete local release preflight: core checks, release CLI smoke, e2e, and the native suite (macOS)
$(MAKE) release-preflight-core
$(MAKE) cli-smoke
$(MAKE) test-e2e
$(MAKE) test-e2e-native
.PHONY: macos-distribution-check
macos-distribution-check: ## Check repo config and GitHub secrets for notarized macOS direct distribution
MARU_RELEASE_REPO="$(MACOS_RELEASE_REPO)" $(NODE) scripts/check-macos-direct-distribution.mjs --github-secrets
.PHONY: macos-distribution-local-check
macos-distribution-local-check: ## Check repo config and local Apple notarization secret files
$(NODE) scripts/check-macos-direct-distribution.mjs
$(NODE) scripts/notarize-local-smoke.mjs --check
.PHONY: macos-passkey-readiness-check
macos-passkey-readiness-check: ## Validate opt-in macOS browser-passkey packaging prerequisites
$(NODE) scripts/check-macos-direct-distribution.mjs --passkeys --require-local-identity
.PHONY: macos-passkey-build
macos-passkey-build: ## Build a locally provisioned macOS browser-passkey bundle (not notarized)
$(NODE) scripts/build-macos-passkeys.mjs
.PHONY: macos-passkey-notarized-build
macos-passkey-notarized-build: ## Build, notarize, and staple the browser-passkey bundle for distribution
$(NODE) scripts/build-macos-passkeys.mjs --notarize
.PHONY: macos-notarize-local
macos-notarize-local: ## Build, sign, and notarize locally with secrets from ~/workspace/work/.maru/secrets/apple
$(NODE) scripts/notarize-local-smoke.mjs "$(or $(TARGET),aarch64-apple-darwin)"
.PHONY: homebrew-update
homebrew-update: ## Render Homebrew cask/formula for RELEASE_TAG into HOMEBREW_TAP_DIR
$(NODE) scripts/update-homebrew-tap.mjs "$(RELEASE_TAG)" "$(HOMEBREW_TAP_DIR)"
.PHONY: homebrew-update-commit
homebrew-update-commit: ## Render and commit Homebrew cask/formula update
$(NODE) scripts/update-homebrew-tap.mjs "$(RELEASE_TAG)" "$(HOMEBREW_TAP_DIR)" --commit
.PHONY: homebrew-update-push
homebrew-update-push: ## Render, commit, and push Homebrew cask/formula update
$(NODE) scripts/update-homebrew-tap.mjs "$(RELEASE_TAG)" "$(HOMEBREW_TAP_DIR)" --commit --push
.PHONY: homebrew-audit
homebrew-audit: ## Audit Maru Homebrew cask and CLI formula in HOMEBREW_TAP_DIR
cd "$(HOMEBREW_TAP_DIR)" && brew audit --cask maru-workspace
cd "$(HOMEBREW_TAP_DIR)" && brew audit --formula maru-cli
.PHONY: homebrew-fetch
homebrew-fetch: ## Fetch Maru Homebrew cask and CLI formula in HOMEBREW_TAP_DIR
cd "$(HOMEBREW_TAP_DIR)" && brew fetch --cask maru-workspace
cd "$(HOMEBREW_TAP_DIR)" && brew fetch maru-cli
# ---------------------------------------------------------------------------
# Verify (the full pre-merge / pre-PR check)
# ---------------------------------------------------------------------------
.PHONY: verify
verify: typecheck lint release-version-check icons-check lint-i18n check-select-chrome check-type-tokens test-ts test-rust fmt-check clippy build-frontend ## Full verification: typecheck + ESLint gate + release versions + generated assets + guards + tests + Rust format check + Rust lint gate + frontend build
# ---------------------------------------------------------------------------
# Clean
# ---------------------------------------------------------------------------
.PHONY: clean
clean: clean-frontend clean-rust ## Remove all build artifacts (keep node_modules + cargo registry cache)
.PHONY: clean-frontend
clean-frontend: ## Remove vite dist + tsbuildinfo
rm -rf dist
find . -name '*.tsbuildinfo' -not -path './node_modules/*' -delete
.PHONY: clean-rust
clean-rust: ## Remove cargo target/
cd $(TAURI_DIR) && $(CARGO) clean
.PHONY: clean-tauri-debug
clean-tauri-debug: ## Prune oversized src-tauri/target/debug artifacts
$(PNPM) clean:tauri-debug -- --force
.PHONY: distclean
distclean: clean ## Also remove node_modules + tauri stub icon
rm -rf node_modules
rm -rf $(TAURI_DIR)/icons