What:新增 tools/swagger-detect.sh,判斷一個專案有沒有真的啟用 Swagger 文件。 涵蓋 dotnet、nodejs、python 三種技術棧,輸出 support=、stack=、package=、 config= 四類欄位;結束碼 0 支援、1 不支援、2 參數個數不對或專案路徑不存在。 Why:API 文件稽核只對產得出 Swagger 文件的專案有意義。支不支援如果交給 agent 自己看程式碼判斷,同一個專案可能這次說支援、下次說不支援。判定寫成腳本,呼叫端 讀結束碼分支就好,不必自己猜。 How:雙重確認,套件與設定缺一不算支援。第一關在套件宣告檔裡找已知的 Swagger 套件,第二關在原始碼裡找真的把 Swagger 接上去的呼叫或裝飾子。設定關鍵字一律 挑接線動作,不挑 import 或 require——光是引入套件不代表文件真的掛上去了。裝了 套件卻沒啟用的專案很常見,只看套件會誤判,讓稽核跑在一個根本產不出文件的專案 上。輸出刻意做成一行一個 key=value,呼叫端逐行讀就好。 Who:jsc-review 的 api-doc 技能,以及 jsc-sdlc 實作階段的收尾稽核。
171 lines
6.3 KiB
Bash
Executable File
171 lines
6.3 KiB
Bash
Executable File
#!/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
|