Files
pkg/tools/build-test.sh
jiantw83 338c2da89c feat(tools): 新增 git 還原閘門腳本,建置測試加上唯讀偵測模式
What:新增 `tools/git-guard.sh`,把工作區乾淨檢查與還原序列從技能內文搬進腳本。`tools/build-test.sh` 加上 `--detect` 唯讀模式,只偵測建置與測試指令,不執行。

Why:還原會執行 `git clean -fd`。這個指令刪掉未追蹤的檔案,沒有 reflog 可救,也沒有任何救回的路徑。前提原本只寫在技能內文,讀的人漏掉一行,就可能對著一個根本不該還原的目錄動手,把整個工作區的未追蹤檔案清光。前提要擋得住,就得寫成程式碼。另一件事是「推不出建置與測試指令」,原本要等所有版本來源檔案都改寫完才發現,使用者又答不出指令時,前面全部作廢,還得走一次還原。

How:`revert` 在跑第一個破壞性指令之前擋五道——目錄存在、git 指令存在、確實是 git 工作樹(不是裸存取庫)、呼叫端明確帶 `--confirm-destructive`、目標不是檔案系統根目錄。任何一道不過就直接結束,工作區一個位元組都不動。`check` 的乾淨判定看整個存取庫,`revert` 只作用在專案目錄以下,判定從嚴、動手從窄。`clean` 用 `-fd` 不用 `-fdx`,被 gitignore 的安裝產物要留著。`--detect` 只做偵測與前置檢查,一個檔案都不寫,結束碼與路由跟執行模式完全一致。

Who:套件批次更新的前置檢查與還原路線。
2026-08-31 11:09:01 +08:00

121 lines
4.5 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env sh
# build-test.sh — 偵測並執行專案的建置與測試,先建置再測試。
# 用法:
# build-test.sh <ecosystem> <project-dir> 偵測後實際執行
# build-test.sh --detect <ecosystem> <project-dir> 只偵測,唯讀,不執行任何建置或測試
# ecosystem:nodejs | python | dotnet
# 偵測方式:
# nodejs → package.json 的 scripts.build 與 scripts.test(npm init 產生的佔位 test 視為沒有)
# python → pytest 設定(pytest.ini、setup.cfg、tox.ini、pyproject.toml)或 tests 目錄與 test_*.py
# dotnet → *.sln 或 *.csproj,建置與測試都用 dotnet
# 為什麼要有 --detect:推不出指令(5)本來要等所有版本都改寫完才發現,使用者又答不出指令時,
# 前面全數作廢還要走還原。--detect 讓呼叫方在動任何檔案之前先問出結果,工作區完全沒被動過。
# --detect 只做偵測與前置檢查,一個檔案都不寫。
# 輸出:底層指令的原始輸出;--detect 模式輸出偵測到的指令名稱,一行一個(nodejs 是 build、test,
# python 是 pytest,dotnet 是 dotnet build、dotnet test)。
# 結束碼(慣例見 README「工具」章的結束碼總表):
# 0 建置與測試都通過;--detect 模式是推得出指令
# 1 參數個數不對
# 2 ecosystem 不認識
# 4 需要的指令不存在(npm、python3、pytest、dotnet)
# 5 推不出任何建置或測試指令——呼叫方要改問使用者
# 6 找不到專案目錄——呼叫方停手
# 其他 建置或測試失敗,帶回失敗指令的結束碼;--detect 模式不會回這一類
# 本工具不用 3:3 保留給「該 ecosystem 沒有來源檔案」,這支改用 5 表達推不出指令。
set -u
DETECT=0
if [ "${1:-}" = "--detect" ]; then
DETECT=1
shift
fi
if [ "$#" -ne 2 ]; then
echo "用法:build-test.sh [--detect] <ecosystem> <project-dir>" >&2
exit 1
fi
eco="$1"
DIR="$2"
NO_COMMAND=5
[ -d "$DIR" ] || { echo "project dir not found: $DIR" >&2; exit 6; }
case "$eco" in
nodejs)
[ -f "$DIR/package.json" ] || { echo "no build or test command inferred for $DIR" >&2; exit "$NO_COMMAND"; }
# package.json 靠 python3 解析。少了 python3 會解出空字串,被誤判成推不出指令,所以先擋。
command -v python3 >/dev/null 2>&1 || { echo "python3 not found" >&2; exit 4; }
scripts=$(python3 - "$DIR/package.json" <<'EOF'
import json,sys
try:
d=json.load(open(sys.argv[1],encoding="utf-8"))
except Exception:
sys.exit(0)
s=(d.get("scripts") or {})
if s.get("build"): print("build")
t=s.get("test") or ""
if t and "no test specified" not in t: print("test")
EOF
)
[ -n "$scripts" ] || { echo "no build or test command inferred for $DIR" >&2; exit "$NO_COMMAND"; }
command -v npm >/dev/null 2>&1 || { echo "npm not found" >&2; exit 4; }
if [ "$DETECT" -eq 1 ]; then
printf '%s\n' "$scripts"
exit 0
fi
for s in $scripts; do
( cd "$DIR" && npm run --if-present "$s" ) || exit $?
done
exit 0
;;
python)
has_tests=0
for f in pytest.ini setup.cfg tox.ini pyproject.toml; do
if [ -f "$DIR/$f" ] && grep -q 'pytest' "$DIR/$f" 2>/dev/null; then has_tests=1; fi
done
[ -d "$DIR/tests" ] && has_tests=1
if [ "$has_tests" -eq 0 ]; then
found=$(find "$DIR" -maxdepth 3 -name 'test_*.py' 2>/dev/null | head -n 1)
[ -n "$found" ] && has_tests=1
fi
[ "$has_tests" -eq 1 ] || { echo "no build or test command inferred for $DIR" >&2; exit "$NO_COMMAND"; }
if command -v pytest >/dev/null 2>&1; then
if [ "$DETECT" -eq 1 ]; then
echo "pytest"
exit 0
fi
( cd "$DIR" && pytest )
exit $?
elif command -v python3 >/dev/null 2>&1 && python3 -m pytest --version >/dev/null 2>&1; then
if [ "$DETECT" -eq 1 ]; then
echo "python3 -m pytest"
exit 0
fi
( cd "$DIR" && python3 -m pytest )
exit $?
else
echo "pytest not found" >&2
exit 4
fi
;;
dotnet)
projs=$(find "$DIR" -maxdepth 3 \( -name '*.sln' -o -name '*.csproj' \) 2>/dev/null)
[ -n "$projs" ] || { echo "no build or test command inferred for $DIR" >&2; exit "$NO_COMMAND"; }
command -v dotnet >/dev/null 2>&1 || { echo "dotnet not found" >&2; exit 4; }
if [ "$DETECT" -eq 1 ]; then
echo "dotnet build"
echo "dotnet test"
exit 0
fi
( cd "$DIR" && dotnet build ) || exit $?
( cd "$DIR" && dotnet test ) || exit $?
exit 0
;;
*) echo "unknown ecosystem: $eco" >&2; exit 2 ;;
esac