feat(review): 新增 API 文件稽核技能,第 5 組擴充註解的條列、導向與標示規則 #11

Merged
admin merged 6 commits from feat/api-doc-audit/main into develop 2026-08-27 08:54:29 +00:00
Showing only changes of commit 57ac9cdcd9 - Show all commits
+170
View File
@@ -0,0 +1,170 @@
#!/usr/bin/env sh
# swagger-detect.sh — 判斷一個專案有沒有真的啟用 Swagger(OpenAPI)文件。
# 用法:swagger-detect.sh [專案路徑](預設目前目錄)
#
# 規則(雙重確認,缺一不算支援):
# 1. 套件:在套件宣告檔裡找得到已知的 Swagger 套件。
# 2. 設定:在原始碼裡找得到實際把 Swagger 接上去的呼叫或裝飾子。
# 只找到套件不算支援。裝了沒啟用的專案很常見,只看套件會誤判,
# 讓 api-doc 稽核跑在一個根本產不出文件的專案上。
# 設定關鍵字一律挑「接線動作」,不挑 import 或 require。光是引入套件
# 不代表文件真的掛上去了。
#
# 涵蓋範圍:
# dotnet 套件 Swashbuckle.AspNetCore、NSwag.AspNetCore
# 設定 AddSwaggerGen、UseSwagger、AddOpenApiDocument、UseOpenApi
# nodejs 套件 swagger-ui-express、@nestjs/swagger、fastify-swagger(含 @fastify/swagger)
# 設定 SwaggerModule.setup、swaggerUi.setup、swaggerUi.serve、
# register 進 fastify 的 swagger 外掛
# python 套件 fastapi、flasgger、drf-spectacular
# 設定 FastAPI( 建立 app、Swagger( 掛上 flasgger、SPECTACULAR_SETTINGS、
# SpectacularAPIView
#
# 輸出(純文字,一行一個 key=value,呼叫端逐行讀就好):
# 第一行永遠是 support=yes 或 support=no。
# 之後每一組偵測到套件的技術棧輸出三行:
# stack=dotnet|nodejs|python
# package=命中的套件名,多個以半形逗號相連
# config=命中的設定關鍵字,多個以半形逗號相連;沒命中就是空字串
# 一個套件都沒找到時只有 support=no 一行。
# 範例:
# support=yes
# stack=dotnet
# package=Swashbuckle.AspNetCore
# config=AddSwaggerGen,UseSwagger
#
# 結束碼:
# 0 支援(至少一個技術棧同時命中套件與設定)
# 1 不支援(沒有任何技術棧同時命中)
# 2 參數個數不對、專案路徑不存在,或環境缺 grep
#
# 護欄:
# 掃描一律跳過 node_modules、.git、bin、obj、dist、build、venv、.venv、
# __pycache__、packages、vendor,避免把相依套件自己的原始碼當成專案設定。
# 錯誤訊息一律印繁中到 stderr,正常輸出只走 stdout。
set -u
if [ "$#" -gt 1 ]; then
echo "用法:swagger-detect.sh [專案路徑]" >&2
exit 2
fi
DIR="${1:-.}"
[ -d "$DIR" ] || { echo "錯誤:找不到專案路徑 $DIR。請確認路徑後重試。" >&2; exit 2; }
command -v grep >/dev/null 2>&1 || { echo "錯誤:環境缺 grep,無法掃描。" >&2; exit 2; }
EX1=--exclude-dir=node_modules
EX2=--exclude-dir=.git
EX3=--exclude-dir=bin
EX4=--exclude-dir=obj
EX5=--exclude-dir=dist
EX6=--exclude-dir=build
EX7=--exclude-dir=venv
EX8=--exclude-dir=.venv
EX9=--exclude-dir=__pycache__
EX10=--exclude-dir=packages
EX11=--exclude-dir=vendor
# scan <延伸正規表示式> <副檔名樣式…>:命中回 0,沒命中回 1。
scan() {
pattern="$1"
shift
for inc in "$@"; do
if grep -R -l -E -i \
"$EX1" "$EX2" "$EX3" "$EX4" "$EX5" "$EX6" \
"$EX7" "$EX8" "$EX9" "$EX10" "$EX11" \
--include="$inc" -e "$pattern" "$DIR" >/dev/null 2>&1; then
return 0
fi
done
return 1
}
# append <既有清單> <新項目>:以半形逗號相連後印出。
append() {
if [ -z "$1" ]; then
printf '%s' "$2"
else
printf '%s,%s' "$1" "$2"
fi
}
SUPPORT=no
RECORDS=""
# collect <技術棧> <套件清單> <設定清單>:有套件才留紀錄,兩者都有才算支援。
collect() {
[ -n "$2" ] || return 0
RECORDS="${RECORDS}stack=$1
package=$2
config=$3
"
[ -n "$3" ] && SUPPORT=yes
return 0
}
# ── dotnet ────────────────────────────────────────────────────────────────
DOTNET_PKG=""
DOTNET_CFG=""
for pkg in 'Swashbuckle\.AspNetCore:Swashbuckle.AspNetCore' 'NSwag\.AspNetCore:NSwag.AspNetCore'; do
if scan "${pkg%%:*}" '*.csproj' '*.fsproj' '*.vbproj' '*.props' 'packages.config'; then
DOTNET_PKG=$(append "$DOTNET_PKG" "${pkg##*:}")
fi
done
if [ -n "$DOTNET_PKG" ]; then
for cfg in AddSwaggerGen UseSwagger AddOpenApiDocument UseOpenApi; do
if scan "$cfg" '*.cs' '*.fs' '*.vb'; then
DOTNET_CFG=$(append "$DOTNET_CFG" "$cfg")
fi
done
fi
collect dotnet "$DOTNET_PKG" "$DOTNET_CFG"
# ── nodejs ────────────────────────────────────────────────────────────────
NODE_PKG=""
NODE_CFG=""
for pkg in swagger-ui-express @nestjs/swagger fastify-swagger @fastify/swagger; do
if scan "\"$pkg\"" 'package.json'; then
NODE_PKG=$(append "$NODE_PKG" "$pkg")
fi
done
if [ -n "$NODE_PKG" ]; then
# 每一項都是「接線動作」:掛路由或註冊外掛,不是 import。
for cfg in 'SwaggerModule\.setup:SwaggerModule.setup' \
'swaggerUi\.setup:swaggerUi.setup' \
'swaggerUi\.serve:swaggerUi.serve' \
'register\(.*swagger:register(swagger)'; do
if scan "${cfg%%:*}" '*.js' '*.mjs' '*.cjs' '*.ts'; then
NODE_CFG=$(append "$NODE_CFG" "${cfg##*:}")
fi
done
fi
collect nodejs "$NODE_PKG" "$NODE_CFG"
# ── python ────────────────────────────────────────────────────────────────
PY_PKG=""
PY_CFG=""
for pkg in fastapi flasgger drf-spectacular; do
if scan "$pkg" 'requirements*.txt' 'pyproject.toml' 'Pipfile' 'setup.py' 'setup.cfg'; then
PY_PKG=$(append "$PY_PKG" "$pkg")
fi
done
if [ -n "$PY_PKG" ]; then
for cfg in 'FastAPI\(:FastAPI(' \
'Swagger\(:Swagger(' \
'SPECTACULAR_SETTINGS:SPECTACULAR_SETTINGS' \
'SpectacularAPIView:SpectacularAPIView'; do
if scan "${cfg%%:*}" '*.py'; then
PY_CFG=$(append "$PY_CFG" "${cfg##*:}")
fi
done
fi
collect python "$PY_PKG" "$PY_CFG"
# ── 輸出 ──────────────────────────────────────────────────────────────────
echo "support=$SUPPORT"
[ -n "$RECORDS" ] && printf '%s' "$RECORDS"
[ "$SUPPORT" = yes ] && exit 0
exit 1