From f5d6ec0bff7a4c94b7f70e3953973514a8a9626f Mon Sep 17 00:00:00 2001 From: Jeffery Date: Fri, 21 Aug 2026 13:08:43 +0800 Subject: [PATCH] =?UTF-8?q?feat(pkg):=20=E5=8C=AF=E5=85=A5=20jsc-pkg=20?= =?UTF-8?q?=E6=8A=80=E8=83=BD=E7=B5=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- skills/pkg-update/SKILL.md | 22 ++++++++++++++ tools/latest-version.sh | 29 +++++++++++++++++++ tools/list-packages.sh | 59 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 skills/pkg-update/SKILL.md create mode 100755 tools/latest-version.sh create mode 100755 tools/list-packages.sh diff --git a/skills/pkg-update/SKILL.md b/skills/pkg-update/SKILL.md new file mode 100644 index 0000000..5eab1c0 --- /dev/null +++ b/skills/pkg-update/SKILL.md @@ -0,0 +1,22 @@ +--- +name: pkg-update +description: Update every external package of a project to its latest stable version using list-packages.sh and latest-version.sh, then run build and tests. Revert all changes on failure. Supports nodejs, python, and dotnet projects. Use for dependency refresh; not for adding or removing packages. +--- + +# pkg-update — 套件批次更新 + +## 步驟 + +1. `tools/list-packages.sh {專案目錄}` 取得所有外部套件(ecosystem / name / current)。 +2. 確認工作樹乾淨:有未認可變更就先停止並回報(避免還原時誤傷)。 +3. 逐 ecosystem 更新,此步驟**必須以 sub agent 執行**(每個 ecosystem 一個 sub agent): + 1. 對每個套件 `tools/latest-version.sh {ecosystem} {name}` 查最新穩定版。 + 2. 改寫版本來源檔(package.json / requirements.txt / pyproject.toml / *.csproj)。 + 3. 重新解析安裝(`npm install` / `pip install -r` / `dotnet restore`)。 +4. 執行建置與測試: + - nodejs:`npm run build`(如有)+ `npm test`(如有)。 + - python:`pytest`(如有測試)。 + - dotnet:`dotnet build` + `dotnet test`。 + - 推斷不出指令時依 `jsc-ask:ask` 規則詢問使用者。 +5. 建置或測試失敗 → 還原全部變更(`git checkout -- .` 與清除未追蹤的 lock 變更),回報失敗套件與錯誤摘要。 +6. 成功 → 回報更新清單(套件 / 舊版 / 新版),交由 `jsc-git:commit` 認可。 diff --git a/tools/latest-version.sh b/tools/latest-version.sh new file mode 100755 index 0000000..81a3e98 --- /dev/null +++ b/tools/latest-version.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env sh +# latest-version.sh — 查詢套件最新且穩定的版本號。 +# 用法: latest-version.sh +# ecosystem: nodejs | python | dotnet +# 輸出: 版本號一行;查不到 exit 4 +set -u +eco="${1:?ecosystem required (nodejs|python|dotnet)}" +name="${2:?package name required}" + +case "$eco" in + nodejs) + v=$(curl -sSf "https://registry.npmjs.org/$name/latest" 2>/dev/null \ + | python3 2>/dev/null -c 'import json,sys;print(json.load(sys.stdin).get("version",""))') ;; + python) + v=$(curl -sSf "https://pypi.org/pypi/$name/json" 2>/dev/null \ + | python3 2>/dev/null -c 'import json,sys;print(json.load(sys.stdin)["info"]["version"])') ;; + dotnet) + lname=$(printf '%s' "$name" | tr '[:upper:]' '[:lower:]') + v=$(curl -sSf "https://api.nuget.org/v3-flatcontainer/$lname/index.json" 2>/dev/null \ + | python3 2>/dev/null -c ' +import json,sys,re +vs=json.load(sys.stdin).get("versions",[]) +stable=[x for x in vs if re.match(r"^[0-9]+(\.[0-9]+)*$",x)] +print((stable or vs)[-1] if (stable or vs) else "")') ;; + *) echo "unknown ecosystem: $eco" >&2; exit 2 ;; +esac + +[ -n "${v:-}" ] || { echo "not found: $eco/$name" >&2; exit 4; } +printf '%s\n' "$v" diff --git a/tools/list-packages.sh b/tools/list-packages.sh new file mode 100755 index 0000000..ee12fc1 --- /dev/null +++ b/tools/list-packages.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env sh +# list-packages.sh — 列出專案的所有外部套件與目前版本。 +# 用法: list-packages.sh [專案目錄](預設目前目錄) +# 輸出(TSV): ecosystemnamecurrent +# 支援: nodejs(package.json)、python(requirements.txt / pyproject.toml)、dotnet(*.csproj) +set -u +DIR="${1:-.}" + +# 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