feat(installer): preserve local skill customizations

This commit is contained in:
2026-07-03 11:27:12 +08:00
parent 716d41b36e
commit af012140d3
3 changed files with 118 additions and 11 deletions
+92 -6
View File
@@ -1,8 +1,8 @@
#!/usr/bin/env bash
# ============================================================
# spec-coding-skills 安装/更新脚本
# 用法: bash <(curl -sL <raw-url>/install.sh) [codex|claude|both]
# 或: bash install.sh [codex|claude|both|目标目录]
# 用法: bash <(curl -sL <raw-url>/install.sh) [codex|claude|both] [--force-overwrite]
# 或: bash install.sh [codex|claude|both|目标目录] [--force-overwrite]
# ============================================================
set -euo pipefail
@@ -23,7 +23,30 @@ 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"
@@ -70,6 +93,53 @@ resolve_layout() {
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"
@@ -79,14 +149,23 @@ sync_file() {
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
cp "$dst" "$dst.local.bak"
cp "$src" "$dst"
log_warn "$update_msg"
TOTAL_UPDATED=$((TOTAL_UPDATED + 1))
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
}
@@ -206,6 +285,7 @@ echo " 📦 版本: $VERSION"
echo ""
echo " ✨ 新增: $TOTAL_NEW"
echo " 🔄 更新: ${TOTAL_UPDATED}(本地版本已备份为 .local.bak)"
echo " 🛡️ 保留本地差异: $TOTAL_PRESERVED"
echo " ⏭️ 无变化: $TOTAL_SKIPPED"
echo ""
@@ -214,3 +294,9 @@ if [ "$TOTAL_UPDATED" -gt 0 ]; then
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