Files
Louisss ecc2c84f13 docs: fix references to non-existent files (#821)
Remove the dead `make docs-cli` target (scripts/regen_cli_docs.py
never existed); point desktop/portable/README.md at the real
octop-desktop.yml workflow and desktop/portable/.gitignore; drop the
non-existent api/jwt_tokens.py from AGENTS.md.

Closes #789
2026-09-19 20:44:55 +08:00

338 lines
13 KiB
Makefile

# Makefile for Octop
# Usage:
# make - Show this help
# make all - format (BE+FE) + backend lint + typecheck + test (ship bar)
# make build - Build frontend + Python wheel
# make publish - Build + upload to PyPI
#
# Prerequisites:
# - Node.js + npm (frontend)
# - Python 3.12+ (backend)
# - uv (recommended) or pip
# - twine (publish: pip install twine / make install-tools)
SHELL := /bin/bash
.DEFAULT_GOAL := help
REPO_ROOT := $(shell pwd)
DASHBOARD_DIR := $(REPO_ROOT)/dashboard
DASHBOARD_DEST := $(REPO_ROOT)/src/octop/dashboard
DIST_DIR := $(REPO_ROOT)/dist
# PyPI repository (override: make publish PYPI_REPO=testpypi)
PYPI_REPO ?= pypi
PYPI_TOKEN ?=
# Prefer uv when available; fall back to plain commands (CI / pip install -e ".[dev]")
UV := $(shell command -v uv 2>/dev/null)
RUN := $(if $(UV),uv run,)
PYTHON := $(if $(UV),uv run python,python3)
PIP := $(if $(UV),uv pip,python3 -m pip)
# Parallel workers for make test / make test-fast (override: make test PYTEST_JOBS=4)
PYTEST_JOBS ?= auto
# ─── Help ────────────────────────────────────────────────────────────────────
.PHONY: help
help:
@echo "Octop Build System"
@echo ""
@echo "Usage: make <target>"
@echo ""
@echo "Build targets:"
@echo " build Build frontend + Python wheel (full package)"
@echo " build-frontend Build React dashboard only → $(DASHBOARD_DEST)"
@echo " build-wheel Build Python wheel only (assumes frontend built)"
@echo ""
@echo "Publish targets:"
@echo " publish Build + upload to PyPI (PYPI_TOKEN or prompt)"
@echo " publish-test Build + upload to TestPyPI"
@echo ""
@echo "Development targets:"
@echo " dev Start frontend + backend dev servers"
@echo " dev-frontend Start Vite dev server only"
@echo " dev-backend Start octop run only"
@echo ""
@echo "Online-deps targets (local Octop source + PyPI harness components):"
@echo " install-online Create .venv-online: Octop editable + harness-* from PyPI"
@echo " test-online pytest against .venv-online (not live)"
@echo " run-online Start octop run from .venv-online"
@echo ""
@echo "Quality targets (ship bar):"
@echo " all format-all + lint + typecheck + test (backend lint/typecheck/test)"
@echo " precommit fast change-aware gate for the git pre-commit hook (testmon-scoped tests)"
@echo " lint Ruff check + format check (src, tests)"
@echo " format Ruff auto-fix + format (src, tests)"
@echo " typecheck mypy --strict src/octop"
@echo " test pytest -m \"not live\""
@echo " test-live pytest -m live"
@echo ""
@echo "Quality targets (frontend):"
@echo " lint-frontend ESLint + Prettier check"
@echo " format-frontend Prettier write"
@echo " typecheck-frontend tsc -b (project references)"
@echo ""
@echo "Quality targets (full stack):"
@echo " lint-all lint + lint-frontend"
@echo " format-all format + format-frontend (also first step of make all)"
@echo " typecheck-all typecheck + typecheck-frontend"
@echo " check-all lint-all + typecheck-all + test"
@echo ""
@echo "Utility targets:"
@echo " install-hooks Point git to .githooks (pre-commit: make all + dashboard build)"
@echo " install Install Python dev dependencies (alias: install-dev)"
@echo " install-dev uv sync / pip install -e \".[dev]\""
@echo " install-tools Install build + twine for publishing"
@echo " clean Remove build artifacts and caches"
@echo " clean-online Remove the .venv-online environment"
@echo " version Show current project version"
# ─── Build ───────────────────────────────────────────────────────────────────
.PHONY: build
build: build-frontend build-wheel
.PHONY: build-frontend
build-frontend:
@echo "[build-frontend] Installing npm dependencies..."
cd $(DASHBOARD_DIR) && npm ci
@echo "[build-frontend] Building dashboard (output: $(DASHBOARD_DEST))..."
cd $(DASHBOARD_DIR) && NODE_ENV=production NODE_OPTIONS="--max-old-space-size=2048" npm run build
@echo "[build-frontend] Done."
.PHONY: build-wheel
build-wheel:
@echo "[build-wheel] Generating README.pypi.md (CHANGELOG + README)..."
@{ cat $(REPO_ROOT)/CHANGELOG.md; echo; cat $(REPO_ROOT)/README.md; } > $(REPO_ROOT)/README.pypi.md
@cp $(REPO_ROOT)/pyproject.toml $(REPO_ROOT)/pyproject.toml.bak
@$(PYTHON) -c "from pathlib import Path; p=Path('$(REPO_ROOT)/pyproject.toml'); p.write_text(p.read_text().replace('readme = \"README.md\"', 'readme = \"README.pypi.md\"', 1))"
@echo "[build-wheel] Cleaning previous build artifacts..."
rm -rf $(DIST_DIR)/*
rm -rf $(REPO_ROOT)/build
@echo "[build-wheel] Building wheel + sdist..."
@set -e; \
status=0; \
if [ -n "$(UV)" ]; then \
uv build --out-dir $(DIST_DIR) . || status=$$?; \
else \
$(PIP) install --quiet build; \
$(PYTHON) -m build --no-isolation --outdir $(DIST_DIR) . || status=$$?; \
fi; \
mv $(REPO_ROOT)/pyproject.toml.bak $(REPO_ROOT)/pyproject.toml; \
rm -f $(REPO_ROOT)/README.pypi.md; \
exit $$status
@echo "[build-wheel] Done. Artifacts in: $(DIST_DIR)/"
@ls -lh $(DIST_DIR)/
# ─── Publish ─────────────────────────────────────────────────────────────────
.PHONY: publish
publish: build
@echo "[publish] Uploading to $(PYPI_REPO)..."
@if ! command -v twine > /dev/null 2>&1; then \
echo "[publish] twine not found. Installing..."; \
$(PIP) install --quiet twine; \
fi
@_token="$(PYPI_TOKEN)"; \
if [ -z "$$_token" ]; then \
echo ""; \
printf " PYPI_TOKEN is not set. Enter your PyPI API token (pypi-xxx): "; \
read -r _token; \
if [ -z "$$_token" ]; then \
echo "[publish] No token provided. Aborting."; \
exit 1; \
fi; \
fi; \
$(if $(UV),uv run,) twine upload \
$(if $(filter testpypi,$(PYPI_REPO)),--repository testpypi,) \
--username __token__ --password "$$_token" \
$(DIST_DIR)/*
@echo "[publish] Upload complete."
.PHONY: publish-test
publish-test:
$(MAKE) publish PYPI_REPO=testpypi
# ─── Development ─────────────────────────────────────────────────────────────
.PHONY: dev
dev:
@echo "[dev] Starting frontend and backend dev servers (Ctrl-C to stop both)..."
@trap 'kill 0' SIGINT; \
(cd $(DASHBOARD_DIR) && npm run dev) & \
($(RUN) octop run) & \
wait
.PHONY: dev-frontend
dev-frontend:
cd $(DASHBOARD_DIR) && npm run dev
.PHONY: dev-backend
dev-backend:
$(RUN) octop run
# ─── Online-deps dev (local Octop source + PyPI harness components) ───────────
# Keeps a dedicated .venv-online alongside .venv. Octop itself is installed
# editable (-e) so source edits are live; the harness-* siblings are pulled
# from PyPI (--no-sources ignores the editable local paths in uv.lock).
# Re-run install-online only when a dependency version changes.
VENV_ONLINE := $(REPO_ROOT)/.venv-online
PY_ONLINE := $(VENV_ONLINE)/bin
.PHONY: install-online
install-online:
@echo "[install-online] Creating $(VENV_ONLINE) (local Octop + PyPI harness deps)..."
uv venv $(VENV_ONLINE) --python 3.12
uv pip install --no-sources --python $(PY_ONLINE)/python -e ".[dev]"
@echo "[install-online] Done. Octop=editable(local), harness-*=PyPI."
.PHONY: test-online
test-online:
@echo "[test-online] pytest against $(VENV_ONLINE) (not live)..."
$(PY_ONLINE)/pytest -m "not live"
.PHONY: run-online
run-online:
$(PY_ONLINE)/octop run
# ─── Quality (backend) ───────────────────────────────────────────────────────
.PHONY: all
all: format-all lint typecheck test
.PHONY: lint
lint:
@echo "[lint] Ruff check..."
$(RUN) ruff check src tests
@echo "[lint] Ruff format check..."
$(RUN) ruff format --check src tests
.PHONY: format
format:
@echo "[format] Ruff fix + format..."
$(RUN) ruff check --fix src tests
$(RUN) ruff format src tests
.PHONY: typecheck
typecheck:
@echo "[typecheck] mypy..."
$(RUN) mypy src/octop
.PHONY: test
test:
@echo "[test] pytest (not live, -n $(PYTEST_JOBS))..."
$(RUN) pytest -n $(PYTEST_JOBS) -m "not live"
.PHONY: test-fast
test-fast:
@echo "[test-fast] pytest (not live, not slow, -n $(PYTEST_JOBS))..."
$(RUN) pytest -n $(PYTEST_JOBS) -m "not live and not slow"
.PHONY: test-live
test-live:
@echo "[test] pytest (live)..."
$(RUN) pytest -m live
# Fast, change-aware gate for the local pre-commit hook. Runs ruff/mypy (cheap
# and kept full for accuracy) plus only the tests affected by changed files via
# pytest-testmon. The full suite still runs in CI (`make all`); this is local
# feedback only, so a missed cross-module impact is caught there.
.PHONY: precommit
precommit: format-all lint typecheck test-affected
# NOTE: do NOT pass `-m` here — pytest-testmon deactivates its affected-test
# selection whenever a marker expression is present, which would fall back to
# the full suite. Live tests under tests/live/ auto-skip when credentials are
# absent, so running them unscoped is cheap and safe; the CI `make all` gate
# keeps `-m "not live"` for the authoritative full run.
#
# NOTE: testmon detects changes via `git ls-files -m` (worktree vs index), which
# is empty once changes are staged — exactly the pre-commit state — so it would
# silently run zero tests. The patch in conftest.py / tests/support/
# testmon_staged_changes.py redirects detection to `git diff HEAD` (staged +
# unstaged) so the gate actually fires on a commit's changes.
.PHONY: test-affected
test-affected:
@echo "[test] pytest (testmon: only tests affected by changes)..."
$(RUN) pytest --testmon
# ─── Quality (frontend) ──────────────────────────────────────────────────────
.PHONY: lint-frontend
lint-frontend:
@echo "[lint-frontend] ESLint..."
cd $(DASHBOARD_DIR) && npm run lint
@echo "[lint-frontend] Prettier check..."
cd $(DASHBOARD_DIR) && npm run format:check
.PHONY: format-frontend
format-frontend:
@echo "[format-frontend] Prettier..."
cd $(DASHBOARD_DIR) && npm run format
.PHONY: typecheck-frontend
typecheck-frontend:
@echo "[typecheck-frontend] tsc..."
cd $(DASHBOARD_DIR) && npx tsc -b
# ─── Quality (full stack) ────────────────────────────────────────────────────
.PHONY: lint-all
lint-all: lint lint-frontend
.PHONY: format-all
format-all: format format-frontend
.PHONY: typecheck-all
typecheck-all: typecheck typecheck-frontend
.PHONY: check-all
check-all: lint-all typecheck-all test
# ─── Utilities ───────────────────────────────────────────────────────────────
.PHONY: install-hooks
install-hooks:
@echo "[install-hooks] Setting core.hooksPath=.githooks"
git config core.hooksPath .githooks
@chmod +x "$(REPO_ROOT)/.githooks/"* 2>/dev/null || true
@echo "[install-hooks] Done. Pre-commit will run: make precommit (format-all + lint + typecheck + testmon-scoped tests), dashboard build"
@echo "[install-hooks] Bypass: SKIP_PRECOMMIT=1 git commit … or git commit --no-verify"
.PHONY: install install-dev
install install-dev:
@echo "[install] Installing Python dev dependencies..."
ifdef UV
uv sync
else
$(PIP) install -e ".[dev]"
endif
.PHONY: install-tools
install-tools:
$(PIP) install --quiet build twine
@echo "[install-tools] build and twine installed."
.PHONY: clean
clean:
@echo "[clean] Removing build artifacts..."
rm -rf $(DIST_DIR)/*
rm -rf $(DASHBOARD_DIR)/dist
rm -rf $(REPO_ROOT)/build
rm -rf $(REPO_ROOT)/*.egg-info
rm -rf $(REPO_ROOT)/src/*.egg-info
rm -rf $(REPO_ROOT)/.mypy_cache $(REPO_ROOT)/.ruff_cache $(REPO_ROOT)/.pytest_cache
rm -rf $(REPO_ROOT)/htmlcov
find $(REPO_ROOT)/src -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
@echo "[clean] Done."
.PHONY: clean-online
clean-online:
@echo "[clean-online] Removing $(VENV_ONLINE)..."
rm -rf $(VENV_ONLINE)
@echo "[clean-online] Done."
.PHONY: version
version:
@$(PYTHON) -c "import pathlib, re; t = pathlib.Path('pyproject.toml').read_text(); m = re.search(r'^version\\s*=\\s*\"([^\"]+)\"', t, re.M); print(m.group(1) if m else 'unknown')"