Files
spec-coding-skills/install.sh
T

303 lines
7.6 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# ============================================================
# spec-coding-skills 安装/更新脚本
# 用法: bash <(curl -sL <raw-url>/install.sh) [codex|claude|both] [--force-overwrite]
# 或: bash install.sh [codex|claude|both|目标目录] [--force-overwrite]
# ============================================================
set -euo pipefail
REPO_URL="https://git.internal.intelligrow.cn/zhangfucai/spec-coding-skills.git"
DEFAULT_MODE="codex"
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_title() { echo -e "${CYAN}$1${NC}"; }
MODE=""
TARGET=""
SKILLS_SRC=""
GUIDE_SRC=""
GUIDE_DST=""
REQUEST="${1:-$DEFAULT_MODE}"
TOTAL_NEW=0
TOTAL_UPDATED=0
TOTAL_SKIPPED=0
TOTAL_PRESERVED=0
INSTALLED_TARGETS=""
FORCE_OVERWRITE="${SPEC_SKILLS_FORCE_OVERWRITE:-0}"
if [ "$#" -gt 0 ]; then
REQUEST="$DEFAULT_MODE"
REQUEST_SET=0
for arg in "$@"; do
case "$arg" in
--force|--force-overwrite)
FORCE_OVERWRITE=1
;;
*)
if [ "$REQUEST_SET" -eq 0 ]; then
REQUEST="$arg"
REQUEST_SET=1
else
log_warn "未知参数: $arg"
exit 1
fi
;;
esac
done
fi
resolve_layout() {
local input="$1"
case "$input" in
""|codex)
MODE="codex"
TARGET=".codex/skills"
SKILLS_SRC=".codex/skills"
GUIDE_SRC="AGENTS.md.template"
GUIDE_DST="AGENTS.md"
;;
claude)
MODE="claude"
TARGET=".claude/skills"
SKILLS_SRC=".claude/skills"
GUIDE_SRC="CLAUDE.md.template"
GUIDE_DST="CLAUDE.md"
;;
both)
log_warn "both 不是单一布局,请在主流程中单独处理"
exit 1
;;
*)
TARGET="$input"
case "$TARGET" in
*".claude/skills"*)
MODE="claude"
SKILLS_SRC=".claude/skills"
GUIDE_SRC="CLAUDE.md.template"
GUIDE_DST="CLAUDE.md"
;;
*)
MODE="codex"
SKILLS_SRC=".codex/skills"
GUIDE_SRC="AGENTS.md.template"
GUIDE_DST="AGENTS.md"
;;
esac
;;
esac
}
TMP_DIR=$(mktemp -d)
trap "rm -rf $TMP_DIR" EXIT
file_hash() {
if command -v shasum >/dev/null 2>&1; then
shasum -a 256 "$1" | awk '{print $1}'
elif command -v sha256sum >/dev/null 2>&1; then
sha256sum "$1" | awk '{print $1}'
else
log_warn "缺少 shasum 或 sha256sum,无法进行本地差异保护"
exit 1
fi
}
state_file_for() {
local dst="$1"
local rel_path
rel_path="${dst#$TARGET/}"
if [ "$rel_path" = "$dst" ]; then
rel_path="$(basename "$dst")"
fi
printf '%s/.install-state/%s.sha256' "$TARGET" "$rel_path"
}
write_install_state() {
local src="$1"
local dst="$2"
local state_file
state_file="$(state_file_for "$dst")"
mkdir -p "$(dirname "$state_file")"
file_hash "$src" >"$state_file"
}
is_unmodified_since_last_install() {
local dst="$1"
local state_file
local dst_hash
local expected_hash
state_file="$(state_file_for "$dst")"
[ -f "$state_file" ] || return 1
dst_hash="$(file_hash "$dst")"
expected_hash="$(cat "$state_file")"
[ "$dst_hash" = "$expected_hash" ]
}
sync_file() {
local src="$1"
local dst="$2"
local create_msg="$3"
local update_msg="$4"
if [ ! -f "$dst" ]; then
mkdir -p "$(dirname "$dst")"
cp "$src" "$dst"
write_install_state "$src" "$dst"
log_info "$create_msg"
TOTAL_NEW=$((TOTAL_NEW + 1))
elif ! diff -q "$src" "$dst" >/dev/null 2>&1; then
if [ "$FORCE_OVERWRITE" = "1" ] || is_unmodified_since_last_install "$dst"; then
cp "$dst" "$dst.local.bak"
cp "$src" "$dst"
write_install_state "$src" "$dst"
log_warn "$update_msg"
TOTAL_UPDATED=$((TOTAL_UPDATED + 1))
else
cp "$src" "${dst}.upstream.new"
log_warn "🛡️ 保留本地差异: ${dst};远端新版已写入 ${dst}.upstream.new"
TOTAL_PRESERVED=$((TOTAL_PRESERVED + 1))
fi
else
write_install_state "$src" "$dst"
TOTAL_SKIPPED=$((TOTAL_SKIPPED + 1))
fi
}
sync_guide_file() {
local src="$1"
local dst="$2"
local create_msg="$3"
local skip_msg="$4"
if [ ! -f "$dst" ]; then
mkdir -p "$(dirname "$dst")"
cp "$src" "$dst"
log_info "$create_msg"
TOTAL_NEW=$((TOTAL_NEW + 1))
else
log_info "$skip_msg"
TOTAL_SKIPPED=$((TOTAL_SKIPPED + 1))
fi
}
install_layout() {
local input="$1"
local skill_dir
local skill_name
local src_file
local rel_path
local dst_dir
local dst_file
local tpl_file
local tpl_name
local dst
local guide_src_path
resolve_layout "$input"
if [ ! -d "$TMP_DIR/$SKILLS_SRC" ]; then
log_warn "上游仓库中不存在技能目录: $SKILLS_SRC"
exit 1
fi
log_info "同步 $MODE: $TARGET"
mkdir -p "$TARGET"
for skill_dir in "$TMP_DIR/$SKILLS_SRC"/*/; do
[ -d "$skill_dir" ] || continue
skill_name=$(basename "$skill_dir")
dst_dir="$TARGET/$skill_name"
[ -f "$skill_dir/SKILL.md" ] || continue
while IFS= read -r -d '' src_file; do
rel_path="${src_file#$skill_dir}"
dst_file="$dst_dir/$rel_path"
sync_file \
"$src_file" \
"$dst_file" \
"✨ 新增: $skill_name/$rel_path ($MODE)" \
"🔄 更新: $skill_name/$rel_path ($MODE) (本地版本已备份为 $(basename "$rel_path").local.bak"
done < <(find "$skill_dir" -type f -print0)
done
for tpl_file in "$TMP_DIR/$SKILLS_SRC"/*.template "$TMP_DIR/$SKILLS_SRC"/*.md; do
[ -f "$tpl_file" ] || continue
tpl_name=$(basename "$tpl_file")
dst="$TARGET/$tpl_name"
sync_file \
"$tpl_file" \
"$dst" \
"✨ 新增模板: $tpl_name ($MODE)" \
"🔄 更新模板: $tpl_name ($MODE) (本地版本已备份为 ${tpl_name}.local.bak"
done
guide_src_path="$TMP_DIR/$GUIDE_SRC"
if [ -f "$guide_src_path" ]; then
sync_guide_file \
"$guide_src_path" \
"$GUIDE_DST" \
"✨ 新增项目引导: ${GUIDE_DST}" \
"⏭️ 跳过项目引导: ${GUIDE_DST}(已存在,保持原样)"
fi
INSTALLED_TARGETS="${INSTALLED_TARGETS}${INSTALLED_TARGETS:+, }$TARGET"
}
# ---------- 拉取最新 ----------
log_title "📦 spec-coding-skills 安装/更新"
echo ""
log_info "拉取最新版本..."
git clone --depth 1 --quiet "$REPO_URL" "$TMP_DIR"
VERSION=$(git -C "$TMP_DIR" describe --tags --always 2>/dev/null || git -C "$TMP_DIR" rev-parse --short HEAD)
log_info "版本: $VERSION"
case "$REQUEST" in
both)
log_info "模式: both"
install_layout codex
install_layout claude
;;
*)
resolve_layout "$REQUEST"
log_info "模式: $MODE"
install_layout "$REQUEST"
;;
esac
# ---------- 汇总 ----------
echo ""
log_title "✅ 完成!"
echo ""
echo " 🧭 模式: $REQUEST"
echo " 📁 目标目录: $INSTALLED_TARGETS"
echo " 📦 版本: $VERSION"
echo ""
echo " ✨ 新增: $TOTAL_NEW"
echo " 🔄 更新: ${TOTAL_UPDATED}(本地版本已备份为 .local.bak"
echo " 🛡️ 保留本地差异: $TOTAL_PRESERVED"
echo " ⏭️ 无变化: $TOTAL_SKIPPED"
echo ""
if [ "$TOTAL_UPDATED" -gt 0 ]; then
log_warn "有 ${TOTAL_UPDATED} 个文件被更新,本地修改已备份为 .local.bak"
log_warn "如需恢复本地版本: mv SKILL.md.local.bak SKILL.md"
log_warn "如需对比差异: diff SKILL.md SKILL.md.local.bak"
fi
if [ "$TOTAL_PRESERVED" -gt 0 ]; then
log_warn "有 ${TOTAL_PRESERVED} 个本地改动文件被保护,远端新版已写入 *.upstream.new"
log_warn "如需合并: diff SKILL.md SKILL.md.upstream.new"
log_warn "如需强制覆盖: 重新运行并追加 --force-overwrite,或设置 SPEC_SKILLS_FORCE_OVERWRITE=1"
fi