mirror of
https://github.com/TencentCloud/Octop.git
synced 2026-09-28 19:33:02 +08:00
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
338 lines
13 KiB
Makefile
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')"
|