#!/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