#!/usr/bin/env sh # list-packages.sh — 列出專案的所有外部套件與目前版本。 # 用法:list-packages.sh [專案目錄](預設目前目錄) # 輸出(TSV):ecosystemnamecurrent # 支援:nodejs(package.json)、python(requirements.txt / pyproject.toml)、dotnet(*.csproj) # 結束碼(慣例見 README「工具」章的結束碼總表): # 0 掃描完成(沒有套件時輸出零行) # 1 參數個數不對 # 4 需要的指令不存在(python3) # 6 找不到專案目錄——呼叫方停手 set -u if [ "$#" -gt 1 ]; then echo "用法:list-packages.sh [專案目錄]" >&2 exit 1 fi DIR="${1:-.}" # 先擋找不到專案目錄。少了這道,壞路徑會輸出零行,被誤判成專案沒有套件。 [ -d "$DIR" ] || { echo "project dir not found: $DIR" >&2; exit 6; } # 四種來源檔案都靠 python3 解析。少了 python3 會輸出零行,被誤判成專案沒有套件,所以先擋。 command -v python3 >/dev/null 2>&1 || { echo "python3 not found" >&2; exit 4; } # nodejs: package.json 的 dependencies / devDependencies if [ -f "$DIR/package.json" ]; then python3 - "$DIR/package.json" <<'EOF' import json,sys d=json.load(open(sys.argv[1],encoding="utf-8")) for k in ("dependencies","devDependencies"): for name,ver in (d.get(k) or {}).items(): print(f"nodejs\t{name}\t{ver}") EOF fi # python: requirements.txt if [ -f "$DIR/requirements.txt" ]; then python3 - "$DIR/requirements.txt" <<'EOF' import re,sys for line in open(sys.argv[1],encoding="utf-8"): line=line.split("#")[0].strip() if not line or line.startswith("-"): continue m=re.match(r"([A-Za-z0-9_.\-\[\]]+)\s*(?:(?:==|>=|<=|~=|!=|>|<)\s*([^,;\s]+))?",line) if m: print(f"python\t{m.group(1)}\t{m.group(2) or ''}") EOF fi # python: pyproject.toml 的 [project] dependencies if [ -f "$DIR/pyproject.toml" ]; then python3 - "$DIR/pyproject.toml" <<'EOF' import re,sys try: import tomllib d=tomllib.load(open(sys.argv[1],"rb")) except Exception: sys.exit(0) deps=(d.get("project") or {}).get("dependencies") or [] for dep in deps: m=re.match(r"([A-Za-z0-9_.\-\[\]]+)\s*(?:(?:==|>=|<=|~=|!=|>|<)\s*([^,;\s]+))?",dep) if m: print(f"python\t{m.group(1)}\t{m.group(2) or ''}") EOF fi # dotnet: *.csproj 的 PackageReference find "$DIR" -maxdepth 3 -name '*.csproj' 2>/dev/null | while IFS= read -r proj; do python3 - "$proj" <<'EOF' import re,sys text=open(sys.argv[1],encoding="utf-8").read() for tag in re.findall(r"]*>",text): inc=re.search(r'Include="([^"]+)"',tag) ver=re.search(r'Version="([^"]+)"',tag) if inc: print(f"dotnet\t{inc.group(1)}\t{ver.group(1) if ver else ''}") EOF done exit 0