feat(deploy): update 前檢查 jsc requires 相依版本
This commit is contained in:
Executable
+119
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env sh
|
||||
# check-requires.sh — Check jsc.requires before updating one plugin.
|
||||
set -u
|
||||
|
||||
usage() {
|
||||
echo "用法:check-requires.sh {claude|codex|copilot|antigravity|kiro} {manifest}" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
[ "$#" -eq 2 ] || usage
|
||||
CLI=$1
|
||||
MANIFEST=$2
|
||||
|
||||
case "$CLI" in
|
||||
claude|codex|copilot|antigravity|kiro) ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
|
||||
[ -f "$MANIFEST" ] || {
|
||||
printf 'status=blocked reason=找不到 manifest:%s\n' "$MANIFEST"
|
||||
exit 1
|
||||
}
|
||||
|
||||
JSC_HOME_DIR="${JSC_HOME:-$HOME/.jsc}"
|
||||
LOCAL_DIR="${JSC_LOCAL_PLUGINS:-$JSC_HOME_DIR/plugins}"
|
||||
KIRO_SKILLS="${JSC_KIRO_SKILLS:-$HOME/.kiro/skills}"
|
||||
|
||||
python3 - "$CLI" "$MANIFEST" "$LOCAL_DIR" "$KIRO_SKILLS" <<'PY'
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
cli, manifest, local_dir, kiro_skills = sys.argv[1:5]
|
||||
|
||||
try:
|
||||
with open(manifest, encoding="utf-8") as fh:
|
||||
data = json.load(fh)
|
||||
except Exception as exc:
|
||||
print(f"status=blocked reason=manifest 不是有效 JSON:{manifest}:{exc}")
|
||||
sys.exit(1)
|
||||
|
||||
requires = ((data.get("jsc") or {}).get("requires") or {})
|
||||
if not requires:
|
||||
print("status=ok reason=沒有宣告相依版本")
|
||||
sys.exit(0)
|
||||
|
||||
def version_tuple(value):
|
||||
value = str(value or "").strip()
|
||||
parts = value.split(".")
|
||||
out = []
|
||||
for part in parts[:3]:
|
||||
match = re.match(r"^(\d+)", part)
|
||||
out.append(int(match.group(1)) if match else 0)
|
||||
while len(out) < 3:
|
||||
out.append(0)
|
||||
return tuple(out)
|
||||
|
||||
def current_version(plugin):
|
||||
domain = plugin[4:] if plugin.startswith("jsc-") else plugin
|
||||
candidates = []
|
||||
patterns = []
|
||||
if cli == "claude":
|
||||
registry = os.path.expanduser("~/.claude/plugins/installed_plugins.json")
|
||||
try:
|
||||
with open(registry, encoding="utf-8") as fh:
|
||||
entries = (json.load(fh).get("plugins") or {}).get(f"jsc-{domain}@jsc") or []
|
||||
for entry in entries:
|
||||
path = os.path.join(entry.get("installPath") or "", "plugin.json")
|
||||
with open(path, encoding="utf-8") as fh:
|
||||
version = json.load(fh).get("version", "")
|
||||
if version:
|
||||
return version, path
|
||||
except Exception:
|
||||
pass
|
||||
patterns.append(os.path.expanduser(f"~/.claude/plugins/cache/jsc/jsc-{domain}/*/plugin.json"))
|
||||
elif cli == "codex":
|
||||
patterns.append(os.path.expanduser(f"~/.codex/plugins/cache/jsc/jsc-{domain}/*/plugin.json"))
|
||||
elif cli == "copilot":
|
||||
patterns.append(os.path.expanduser(f"~/.copilot/installed-plugins/jsc/jsc-{domain}/plugin.json"))
|
||||
elif cli == "antigravity":
|
||||
patterns.append(os.path.join(local_dir, domain, "plugin.json"))
|
||||
patterns.append(os.path.expanduser(f"~/.antigravity/plugins/jsc-{domain}/plugin.json"))
|
||||
elif cli == "kiro":
|
||||
patterns.append(os.path.join(kiro_skills, f"jsc-{domain}", "plugin.json"))
|
||||
|
||||
for pattern in patterns:
|
||||
for path in glob.glob(pattern):
|
||||
try:
|
||||
with open(path, encoding="utf-8") as fh:
|
||||
version = json.load(fh).get("version", "")
|
||||
except Exception:
|
||||
continue
|
||||
if version:
|
||||
candidates.append((version_tuple(version), version, path))
|
||||
if not candidates:
|
||||
return "", ""
|
||||
candidates.sort()
|
||||
return candidates[-1][1], candidates[-1][2]
|
||||
|
||||
failures = []
|
||||
for plugin, constraint in sorted(requires.items()):
|
||||
required = str(constraint).strip()
|
||||
minimum = required[2:].strip() if required.startswith(">=") else required
|
||||
current, path = current_version(plugin)
|
||||
if not current:
|
||||
failures.append(f"{plugin} 需要 {required},目前未安裝")
|
||||
continue
|
||||
if version_tuple(current) < version_tuple(minimum):
|
||||
failures.append(f"{plugin} 需要 {required},目前 {current}({path})")
|
||||
|
||||
if failures:
|
||||
print("status=blocked reason=" + ";".join(failures))
|
||||
sys.exit(1)
|
||||
|
||||
print("status=ok reason=相依版本符合:" + ", ".join(f"{k} {v}" for k, v in sorted(requires.items())))
|
||||
sys.exit(0)
|
||||
PY
|
||||
+28
-3
@@ -11,6 +11,7 @@
|
||||
# skip<TAB>{domain}<TAB>{原因} 本地 clone 是開發中的樹,略過 git pull
|
||||
# note<TAB>{cli}<TAB>{原因} 非逐指令的說明(例:kiro 整批改走複製退路的理由)
|
||||
# restart<TAB>{路徑} 這次寫下的重啟狀態檔
|
||||
# requires<TAB>{domain}<TAB>{檢查結果} update 前的 jsc.requires 檢查
|
||||
# result<TAB>{cli}<TAB>{mode}<TAB>{domain 清單}<TAB>{ok|fail}
|
||||
# 結束碼:全部指令成功 0;任一指令失敗 1;參數錯誤 2。skip、note 不算失敗,但呼叫端要據實回報。
|
||||
# marketplace 指令一輪只跑一次:install 與 update 先跑,uninstall 最後跑。
|
||||
@@ -36,6 +37,8 @@
|
||||
# JSC_DEPLOY_DRYRUN 設為 1 等同 -n
|
||||
set -u
|
||||
|
||||
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
|
||||
# Gitea 站台一律讀 GITEA_HOST(技能準則指定的變數),未設定才用正本站台。
|
||||
HOST="${GITEA_HOST:-https://gitea.jsc.idv.tw}"
|
||||
case "$HOST" in http://*|https://*) ;; *) HOST="https://$HOST" ;; esac
|
||||
@@ -89,6 +92,25 @@ restart_gate_sh() {
|
||||
return 1
|
||||
}
|
||||
|
||||
check_requires() { # $1=domain;0=可更新,1=略過這個 domain
|
||||
[ "$MODE" = update ] || return 0
|
||||
sync_local "$1"
|
||||
manifest="$LOCAL_DIR/$1/plugin.json"
|
||||
out=$(sh "$HERE/check-requires.sh" "$CLI" "$manifest" 2>&1)
|
||||
code=$?
|
||||
printf 'requires\t%s\t%s\n' "$1" "$out"
|
||||
if [ "$code" -eq 0 ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ "$code" -eq 1 ]; then
|
||||
printf 'skip\t%s\t%s\n' "$1" "相依版本不符,未更新:$out"
|
||||
return 1
|
||||
fi
|
||||
FAILED=1
|
||||
printf 'skip\t%s\t%s\n' "$1" "相依版本檢查失敗,未更新:$out"
|
||||
return 1
|
||||
}
|
||||
|
||||
# 掛上部署後的重啟閘門。狀態檔的路徑、格式與判讀全在 jsc-hooks 的 restart-gate.sh,
|
||||
# 這裡只轉呼叫它的 require 子命令,比照 jsc-sdlc 轉呼叫 sdlc-gate.sh wp-lock 的慣例。
|
||||
# 兩邊各拼一份格式就會對不上:2026-08-27 這裡曾自己寫四欄 TSV,而 hooks 那端讀的是
|
||||
@@ -175,7 +197,7 @@ marketplace_cli() { # $1=執行檔
|
||||
;;
|
||||
update)
|
||||
run "$1" plugin marketplace update jsc
|
||||
for d in $DOMAINS; do run "$1" plugin update "jsc-$d@jsc"; done
|
||||
for d in $DOMAINS; do check_requires "$d" && run "$1" plugin update "jsc-$d@jsc"; done
|
||||
;;
|
||||
uninstall)
|
||||
for d in $DOMAINS; do run "$1" plugin uninstall "jsc-$d@jsc"; done
|
||||
@@ -199,7 +221,7 @@ deploy_codex() {
|
||||
;;
|
||||
update)
|
||||
run "$bin" plugin marketplace upgrade jsc
|
||||
for d in $DOMAINS; do run "$bin" plugin add "jsc-$d@jsc"; done
|
||||
for d in $DOMAINS; do check_requires "$d" && run "$bin" plugin add "jsc-$d@jsc"; done
|
||||
;;
|
||||
uninstall)
|
||||
for d in $DOMAINS; do run "$bin" plugin remove "jsc-$d@jsc"; done
|
||||
@@ -217,6 +239,7 @@ deploy_antigravity() {
|
||||
run "$bin" plugin install "$LOCAL_DIR/$d"
|
||||
;;
|
||||
update)
|
||||
check_requires "$d" || continue
|
||||
sync_local "$d"
|
||||
run "$bin" plugin uninstall "jsc-$d"
|
||||
run "$bin" plugin install "$LOCAL_DIR/$d"
|
||||
@@ -278,6 +301,7 @@ deploy_kiro() {
|
||||
run_soft "$bin" plugin install "jsc-$d@jsc" || kiro_copy "$d"
|
||||
;;
|
||||
update)
|
||||
check_requires "$d" || continue
|
||||
run_soft "$bin" plugin update "jsc-$d@jsc" || kiro_copy "$d"
|
||||
;;
|
||||
uninstall)
|
||||
@@ -291,7 +315,8 @@ deploy_kiro() {
|
||||
printf 'note\tkiro\t%s\n' "此版本 kiro-cli 沒有 plugin 子指令,改走本地複製(git pull 或 clone 後,複製 skills、tools、references、templates、hooks 與 plugin.json)"
|
||||
for d in $DOMAINS; do
|
||||
case "$MODE" in
|
||||
install|update) kiro_copy "$d" ;;
|
||||
install) kiro_copy "$d" ;;
|
||||
update) check_requires "$d" && kiro_copy "$d" ;;
|
||||
uninstall) run rm -rf "$KIRO_SKILLS/jsc-$d" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
Reference in New Issue
Block a user