以 plugin 形式安裝時,腳本會落在 CLI 的 plugin 快取目錄。 六支腳本原本一律取「腳本位置的上兩層」當技能組根目錄,這時一定推錯。 這一輪例行稽核的第一步就實際踩到:不先手動設環境變數,腳本根本跑不動, 而叫用它們的技能說明也沒提要設,等於留了一個必炸的預設值。 把推導規則抽成共用腳本,依序試環境變數、從目前目錄往上找、 腳本位置的上兩層、家目錄底下的 plugins,並以 gitea.sh 在不在當判準。 marketplace 每個 domain 存取庫都帶一份,單看它會把 domain 誤判成根。 推不出來就結束並指名要設環境變數,同時列出試過的每一個候選, 呼叫端一眼看得出要設什麼。只 clone 單一存取庫的環境留了逃生門: 環境變數有設且是目錄就照用,並在錯誤輸出提醒。 順手補上語言檢查與 manifest 同步兩支腳本的結束碼宣告與環境變數說明。 沒有宣告,呼叫端只能猜;猜錯就把失敗當成功。
128 lines
5.1 KiB
Bash
Executable File
128 lines
5.1 KiB
Bash
Executable File
#!/usr/bin/env sh
|
||
# sync-marketplace.sh — 把一個 plugin 條目寫進正本 marketplace,再同步到所有 domain 存取庫。
|
||
#
|
||
# 用法: sync-marketplace.sh <domain> <repo-url> <description>
|
||
# 例: sync-marketplace.sh doc https://gitea.jsc.idv.tw/plugins/doc.git "文件產出與同步"
|
||
#
|
||
# 行為:
|
||
# 1. 在 plugins/meta 的兩份正本(.claude-plugin/marketplace.json 與
|
||
# .agents/plugins/marketplace.json)插入或更新 jsc-{domain} 條目;已存在就覆寫網址與描述。
|
||
# 條目依 name 排序,格式維持兩格縮排、不轉義非 ASCII、結尾一個換行。
|
||
# 2. 兩份正本與所有副本都由同一份算好的內容複製過去,保證位元組完全一致。
|
||
# 3. 逐一複製到每個 domain 存取庫的相同兩個路徑(含新 domain 自己)。
|
||
# 4. 全部寫完後逐檔比對,有任何一份不一致就報錯。
|
||
#
|
||
# 依賴: **python3**(只用標準函式庫的 json 模組)。
|
||
# 本 repo 的腳本一律 shell 優先,這裡是唯一例外:條目的插入、排序與重新序列化要動 JSON,
|
||
# 純 shell 拼 JSON 會壞掉(縮排、逸出、非 ASCII 描述)。所以保留 python3。
|
||
# python3 不在 PATH 時:腳本不寫任何檔案,印出缺少 python3 的訊息,回 1。
|
||
# 呼叫端要先裝 python3 再重跑,不可改用 sed 手動改 marketplace。
|
||
#
|
||
# 根目錄: 交給 tools/plugins-root.sh 推導(JSC_PLUGINS_ROOT -> 從 $PWD 往上找 ->
|
||
# 腳本位置上兩層 -> $HOME/plugins)。以 plugin 形式安裝時,「腳本位置上兩層」會落在
|
||
# plugin 快取目錄,單靠它一定推錯,所以推導規則抽成共用腳本。
|
||
# 存取庫目錄名: 先找 {root}/{domain},再找 {root}/jsc-{domain}。
|
||
#
|
||
# 輸出: 一行一個實際寫入的檔案路徑(stdout);略過與失敗說明走 stderr。
|
||
# 結束碼: 0=全部寫入且逐檔一致 2=用法錯誤
|
||
# 1=推導不出根目錄、缺 python3、正本讀寫失敗、建不了暫存目錄,
|
||
# 或有檔案比對不一致
|
||
# 3=寫入成功,但有 domain 存取庫不在本機(先跑 sync-domains.sh 再重跑)
|
||
# 環境變數: JSC_PLUGINS_ROOT(根目錄,見 plugins-root.sh)
|
||
set -u
|
||
|
||
usage() {
|
||
echo 'usage: sync-marketplace.sh <domain> <repo-url> <description>' >&2
|
||
exit 2
|
||
}
|
||
|
||
[ "$#" -eq 3 ] || usage
|
||
DOMAIN=$1
|
||
URL=$2
|
||
DESC=$3
|
||
[ -n "$DOMAIN" ] && [ -n "$URL" ] && [ -n "$DESC" ] || usage
|
||
|
||
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||
META=$(CDPATH= cd -- "$HERE/.." && pwd)
|
||
. "$HERE/plugins-root.sh"
|
||
ROOT=$(jsc_plugins_root) || exit 1
|
||
|
||
REL_CLAUDE='.claude-plugin/marketplace.json'
|
||
REL_AGENTS='.agents/plugins/marketplace.json'
|
||
CANON="$META/$REL_CLAUDE"
|
||
|
||
[ -f "$CANON" ] || { echo "找不到正本 marketplace:$CANON" >&2; exit 1; }
|
||
|
||
# 先擋掉缺 python3 的環境,才不會寫到一半才失敗。
|
||
command -v python3 >/dev/null 2>&1 || {
|
||
echo '找不到 python3:本腳本用 python3 的 json 模組改 marketplace 條目。請先安裝 python3 再重跑。' >&2
|
||
exit 1
|
||
}
|
||
|
||
WORKDIR=$(mktemp -d) || exit 1
|
||
trap 'rm -rf "$WORKDIR"' EXIT
|
||
NEW="$WORKDIR/marketplace.json"
|
||
|
||
# 條目的插入與更新交給 json 解析器;用 sed 拼 JSON 會壞掉。
|
||
JSC_MK_DOMAIN="$DOMAIN" JSC_MK_URL="$URL" JSC_MK_DESC="$DESC" \
|
||
JSC_MK_IN="$CANON" JSC_MK_OUT="$NEW" python3 - <<'PY' || { echo "更新正本內容失敗:$CANON" >&2; exit 1; }
|
||
import json, os
|
||
|
||
name = "jsc-" + os.environ["JSC_MK_DOMAIN"]
|
||
entry = {
|
||
"name": name,
|
||
"source": {"source": "url", "url": os.environ["JSC_MK_URL"]},
|
||
"description": os.environ["JSC_MK_DESC"],
|
||
}
|
||
with open(os.environ["JSC_MK_IN"], encoding="utf-8") as fh:
|
||
data = json.load(fh)
|
||
plugins = [p for p in data.get("plugins", []) if p.get("name") != name]
|
||
plugins.append(entry)
|
||
data["plugins"] = sorted(plugins, key=lambda p: p.get("name", ""))
|
||
with open(os.environ["JSC_MK_OUT"], "w", encoding="utf-8") as fh:
|
||
fh.write(json.dumps(data, indent=2, ensure_ascii=False) + "\n")
|
||
PY
|
||
|
||
rc=0
|
||
|
||
write_copy() { # $1=目標檔案
|
||
target=$1
|
||
dir=$(dirname "$target")
|
||
mkdir -p "$dir" 2>/dev/null || { echo "無法建立目錄:$dir" >&2; rc=1; return 1; }
|
||
cp "$NEW" "$target" 2>/dev/null || { echo "無法寫入:$target" >&2; rc=1; return 1; }
|
||
printf '%s\n' "$target"
|
||
}
|
||
|
||
# ---- 1. 兩份正本 ----
|
||
write_copy "$META/$REL_CLAUDE"
|
||
write_copy "$META/$REL_AGENTS"
|
||
|
||
# ---- 2. 每個 domain 存取庫的副本 ----
|
||
DOMAINS=$(sed -n 's/.*"name": *"jsc-\([a-z0-9-]*\)".*/\1/p' "$NEW" | sort -u)
|
||
for d in $DOMAINS; do
|
||
path=""
|
||
for cand in "$ROOT/$d" "$ROOT/jsc-$d"; do
|
||
[ -d "$cand" ] && { path="$cand"; break; }
|
||
done
|
||
if [ -z "$path" ]; then
|
||
echo "存取庫不在本機,略過:$d(先跑 sync-domains.sh)" >&2
|
||
[ "$rc" -eq 0 ] && rc=3
|
||
continue
|
||
fi
|
||
[ "$path" = "$META" ] && continue
|
||
write_copy "$path/$REL_CLAUDE"
|
||
write_copy "$path/$REL_AGENTS"
|
||
done
|
||
|
||
# ---- 3. 逐檔比對,確認位元組一致 ----
|
||
for d in "$META" $(for x in $DOMAINS; do
|
||
for cand in "$ROOT/$x" "$ROOT/jsc-$x"; do [ -d "$cand" ] && { echo "$cand"; break; }; done
|
||
done); do
|
||
for rel in "$REL_CLAUDE" "$REL_AGENTS"; do
|
||
[ -f "$d/$rel" ] || continue
|
||
cmp -s "$NEW" "$d/$rel" || { echo "內容不一致:$d/$rel" >&2; rc=1; }
|
||
done
|
||
done
|
||
|
||
exit $rc
|