Installing kyberforge via the holocron marketplace caches the plugin at a specific version. After every push, the marketplace clone and plugin cache need to be refreshed manually — otherwise new skills added since the last install are invisible until the user runs `claude plugin update` manually. Adds a post-push git hook that pulls the holocron marketplace clone and updates the kyberforge cache automatically after every push, eliminating the manual refresh step. ## Implementation Notes - `scripts/git-hooks/post-push` is the canonical source; `install.sh` now copies all files in `scripts/git-hooks/` into `.git/hooks/` on fresh checkouts, making the pattern extensible for future hooks. - Hook exits 0 on all failures (warns to stderr) — a stale cache refresh never blocks a completed push. - 11 bats tests cover both the hook and the install.sh copy block, using mocked binaries and a temp-tree fixture to avoid touching real state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P87CiC58Ru2PPWYTeXtjHT
120 lines
3.9 KiB
Bash
Executable File
120 lines
3.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Tests for the git hook install block in scripts/install.sh.
|
|
# Runs install.sh from a temp repo to avoid touching the real .git/hooks/.
|
|
set -euo pipefail
|
|
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
PASS=0
|
|
FAIL=0
|
|
|
|
pass() { echo " PASS: $1"; PASS=$((PASS + 1)); }
|
|
fail() { echo " FAIL: $1"; FAIL=$((FAIL + 1)); }
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Temp repo setup
|
|
#
|
|
# install.sh derives REPO_ROOT from the script's own location, so copying it
|
|
# into a temp tree makes REPO_ROOT point there, not at the real checkout.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
TEMP_REPO="$(mktemp -d)"
|
|
TEMP_HOME="$(mktemp -d)"
|
|
trap 'rm -rf "$TEMP_REPO" "$TEMP_HOME"' EXIT
|
|
|
|
mkdir -p "$TEMP_REPO/scripts"
|
|
mkdir -p "$TEMP_REPO/.git/hooks"
|
|
|
|
# Minimal deploy-manifest.sh — empty deploy lists so install.sh reaches the
|
|
# hook-copy block without attempting to copy files that don't exist in the
|
|
# temp tree.
|
|
cat > "$TEMP_REPO/scripts/deploy-manifest.sh" << 'EOF'
|
|
# shellcheck disable=SC2034
|
|
DEPLOY_FILES=()
|
|
DEPLOY_EXECUTABLES=()
|
|
DEPLOY_DIRS=()
|
|
DEPLOY_SKILLS_SRC=".agents/skills"
|
|
EOF
|
|
|
|
# Copy the real install.sh and git-hooks into the temp repo
|
|
cp "$REPO_ROOT/scripts/install.sh" "$TEMP_REPO/scripts/install.sh"
|
|
cp -r "$REPO_ROOT/scripts/git-hooks" "$TEMP_REPO/scripts/git-hooks"
|
|
|
|
HOOKS_SRC="$TEMP_REPO/scripts/git-hooks"
|
|
|
|
run_install() {
|
|
HOME="$TEMP_HOME" bash "$TEMP_REPO/scripts/install.sh" > /dev/null 2>&1
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
echo "--- hook install: files copied to .git/hooks/ ---"
|
|
# ---------------------------------------------------------------------------
|
|
|
|
run_install
|
|
|
|
for hook_file in "$HOOKS_SRC"/*; do
|
|
[[ -f "$hook_file" ]] || continue
|
|
hook_name="$(basename "$hook_file")"
|
|
if [[ -f "$TEMP_REPO/.git/hooks/$hook_name" ]]; then
|
|
pass "$hook_name installed to .git/hooks/"
|
|
else
|
|
fail "$hook_name missing from .git/hooks/"
|
|
fi
|
|
done
|
|
|
|
# ---------------------------------------------------------------------------
|
|
echo ""
|
|
echo "--- hook install: installed hooks are executable ---"
|
|
# ---------------------------------------------------------------------------
|
|
|
|
for hook_file in "$HOOKS_SRC"/*; do
|
|
[[ -f "$hook_file" ]] || continue
|
|
hook_name="$(basename "$hook_file")"
|
|
if [[ -x "$TEMP_REPO/.git/hooks/$hook_name" ]]; then
|
|
pass "$hook_name is executable"
|
|
else
|
|
fail "$hook_name is not executable"
|
|
fi
|
|
done
|
|
|
|
# ---------------------------------------------------------------------------
|
|
echo ""
|
|
echo "--- hook install: directories in git-hooks/ are not installed ---"
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# Add a subdirectory to scripts/git-hooks/ — the install block must skip it
|
|
mkdir -p "$HOOKS_SRC/not-a-hook"
|
|
run_install
|
|
if [[ ! -e "$TEMP_REPO/.git/hooks/not-a-hook" ]]; then
|
|
pass "directories in scripts/git-hooks/ are not copied to .git/hooks/"
|
|
else
|
|
fail "a directory from scripts/git-hooks/ was incorrectly installed"
|
|
fi
|
|
rmdir "$HOOKS_SRC/not-a-hook"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
echo ""
|
|
echo "--- hook install: idempotent ---"
|
|
# ---------------------------------------------------------------------------
|
|
|
|
run_install
|
|
|
|
for hook_file in "$HOOKS_SRC"/*; do
|
|
[[ -f "$hook_file" ]] || continue
|
|
hook_name="$(basename "$hook_file")"
|
|
if diff -q "$hook_file" "$TEMP_REPO/.git/hooks/$hook_name" > /dev/null 2>&1; then
|
|
pass "idempotent: $hook_name content unchanged after second install"
|
|
else
|
|
fail "idempotent: $hook_name content corrupted after second install"
|
|
fi
|
|
if [[ -x "$TEMP_REPO/.git/hooks/$hook_name" ]]; then
|
|
pass "idempotent: $hook_name still executable after second install"
|
|
else
|
|
fail "idempotent: $hook_name lost executable bit after second install"
|
|
fi
|
|
done
|
|
|
|
# ---------------------------------------------------------------------------
|
|
echo ""
|
|
echo "Results: $PASS passed, $FAIL failed"
|
|
[[ $FAIL -eq 0 ]]
|