diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..e1229b1 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# 提交前做轻量检查:Python 格式 + 前端 TypeScript。 +# 全量 pytest 放在 pre-push,避免每次 commit 都跑一遍。 +set -euo pipefail + +cd "$(git rev-parse --show-toplevel)" + +if [ "${SKIP_TESTS:-0}" = "1" ]; then + echo "[pre-commit] SKIP_TESTS=1,跳过检查" + exit 0 +fi + +staged="$(git diff --cached --name-only --diff-filter=ACMR)" + +python_files=() +frontend_changed=0 +while IFS= read -r path; do + [ -z "$path" ] && continue + case "$path" in + *.py) + [ -f "$path" ] && python_files+=("$path") + ;; + web/frontend/*) + frontend_changed=1 + ;; + esac +done <<< "$staged" + +if [ "${#python_files[@]}" -gt 0 ]; then + echo "[pre-commit] 检查 Python 格式..." + uv run --group dev ruff format --check "${python_files[@]}" + echo "[pre-commit] Python 格式通过" +fi + +if [ "$frontend_changed" -eq 1 ]; then + .githooks/run-frontend-tsc.sh +fi diff --git a/.githooks/pre-push b/.githooks/pre-push index 6b117d9..9ae57fd 100755 --- a/.githooks/pre-push +++ b/.githooks/pre-push @@ -17,18 +17,27 @@ if [ "${SKIP_TESTS:-0}" = "1" ]; then fi python_files=() +frontend_changed=0 while read -r local_ref local_sha remote_ref remote_sha; do [ -z "$local_sha" ] && continue if [ "$local_sha" = "0000000000000000000000000000000000000000" ]; then continue fi if [ "$remote_sha" = "0000000000000000000000000000000000000000" ]; then - changed_paths="$(git diff-tree --no-commit-id --name-only -r --root "$local_sha" -- '*.py')" + changed_paths="$(git diff-tree --no-commit-id --name-only -r --root "$local_sha")" else - changed_paths="$(git diff --name-only "$remote_sha..$local_sha" -- '*.py')" + changed_paths="$(git diff --name-only "$remote_sha..$local_sha")" fi while IFS= read -r path; do - [ -n "$path" ] && [ -f "$path" ] && python_files+=("$path") + [ -z "$path" ] && continue + case "$path" in + *.py) + [ -f "$path" ] && python_files+=("$path") + ;; + web/frontend/*) + frontend_changed=1 + ;; + esac done <<< "$changed_paths" done <<< "$push_lines" @@ -38,6 +47,10 @@ if [ "${#python_files[@]}" -gt 0 ]; then echo "[pre-push] Python 格式通过" fi +if [ "$frontend_changed" -eq 1 ]; then + .githooks/run-frontend-tsc.sh +fi + if [ "${TYPECHECK:-0}" = "1" ]; then echo "[pre-push] 运行 Pyright 类型检查..." uv run --group dev pyright diff --git a/.githooks/run-frontend-tsc.sh b/.githooks/run-frontend-tsc.sh new file mode 100755 index 0000000..c55566f --- /dev/null +++ b/.githooks/run-frontend-tsc.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# 与 Docker 构建第一步对齐:tsc -b。失败则阻止提交/推送。 +set -euo pipefail + +ROOT="$(git rev-parse --show-toplevel)" +FRONTEND="$ROOT/web/frontend" + +if ! command -v npm >/dev/null 2>&1; then + echo "[git-hook] 未找到 npm,无法做前端 TypeScript 检查。" + echo "请安装 Node.js,或紧急用 git push --no-verify 绕过。" + exit 1 +fi + +cd "$FRONTEND" +if [ ! -x node_modules/.bin/tsc ]; then + echo "[git-hook] 前端依赖缺失,正在 npm ci..." + npm ci +fi + +echo "[git-hook] 检查前端 TypeScript..." +npm exec -- tsc -b --pretty false +echo "[git-hook] TypeScript 通过" diff --git a/README.md b/README.md index 54e25e7..51739b4 100644 --- a/README.md +++ b/README.md @@ -113,13 +113,17 @@ docker compose --profile test run --rm test 测试各自建 SQLite 内存库;根目录 `conftest.py` 兜底 `DATABASE_URL` / `APP_ENCRYPTION_KEY`,防止误连开发/生产库。迁移链冒烟见 `tests/test_migrations.py`。兑换、下单、支付属于写操作,默认用 mock 做契约测试,不打真实支付。 -启用推送前门禁(Ruff + pytest): +启用提交/推送门禁(改过前端会跑 TypeScript,与 Docker `npm run build` 的 `tsc -b` 一致): ```bash ./scripts/install-git-hooks.sh +./dev.sh frontend-check # 单独跑前端 TypeScript ``` -需要把 Pyright 也纳入时:`TYPECHECK=1 git push`。紧急绕过:`git push --no-verify` 或 `SKIP_TESTS=1 git push`。 +- **pre-commit**:暂存的 Python 文件做 Ruff 格式检查;改了 `web/frontend/` 则跑 `tsc -b` +- **pre-push**:上述检查 + 全量 pytest +- Pyright 默认不开(噪音大):`TYPECHECK=1 git push` +- 紧急绕过:`git push --no-verify` 或 `SKIP_TESTS=1 git push` ## 技术栈 diff --git a/dev.sh b/dev.sh index 3956a72..0f8da1e 100755 --- a/dev.sh +++ b/dev.sh @@ -96,6 +96,11 @@ if [ "${1:-}" = "type-check" ]; then exec uv run --group dev python -m pyright "$@" fi +# 与 Docker 镜像构建第一步对齐:tsc -b。 +if [ "${1:-}" = "frontend-check" ]; then + exec "$ROOT_DIR/.githooks/run-frontend-tsc.sh" +fi + BACKEND_PID="" FRONTEND_PID="" WORKER_PID="" diff --git a/scripts/install-git-hooks.sh b/scripts/install-git-hooks.sh index 5f244cc..b2e1a88 100755 --- a/scripts/install-git-hooks.sh +++ b/scripts/install-git-hooks.sh @@ -3,4 +3,8 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" git -C "$ROOT_DIR" config core.hooksPath .githooks -echo "已启用 .githooks(Ruff 格式检查 + pytest pre-push 门禁;TYPECHECK=1 可启用 Pyright)" +echo "已启用 .githooks:" +echo " pre-commit: 暂存的 Python 格式 + 前端 TypeScript" +echo " pre-push: 上述检查 + pytest" +echo " TYPECHECK=1 git push 可额外跑 Pyright" +echo "紧急绕过:git push --no-verify 或 SKIP_TESTS=1 git push" diff --git a/web/frontend/package.json b/web/frontend/package.json index fa43c51..2ab03a2 100644 --- a/web/frontend/package.json +++ b/web/frontend/package.json @@ -6,6 +6,7 @@ "scripts": { "dev": "vite", "build": "tsc -b && vite build", + "typecheck": "tsc -b", "lint": "eslint .", "preview": "vite preview" },