# Disable set -e inherited from husky's sh -e wrapper.
# Git hooks run with stdout as a closed/broken pipe, causing echo to fail
# with "write error: Bad file descriptor". Under set -e this silently kills
# the entire hook. This script has explicit exit 1 calls for real failures.
set +e
# Redirect stdout to stderr so user-facing messages reach the terminal
# instead of git's closed stdout pipe.
exec 1>&2
echo "π§ͺ Running tests before push to ensure code quality..."
echo "π‘ This prevents broken code from being pushed to GitHub."
echo ""
# Source cargo environment if available (for Rust tests)
if [ -f "$HOME/.cargo/env" ]; then
. "$HOME/.cargo/env"
fi
# Auto-detect Docker availability and skip Docker tests if not fully functional
if ! docker buildx version >/dev/null 2>&1; then
export SKIP_DOCKER_TESTS=true
echo "Docker buildx not available - Docker tests will be skipped."
echo ""
fi
# Detect if this push contains only tags (read refs from stdin)
TMP_REFS=$(mktemp 2>/dev/null || echo ./.prepush_tmp)
cat - > "$TMP_REFS"
TAGS_ONLY=true
if [ ! -s "$TMP_REFS" ]; then
# If no stdin provided, assume this is not a tags-only push
TAGS_ONLY=false
else
while read -r LOCAL_REF LOCAL_SHA REMOTE_REF REMOTE_SHA; do
case "$LOCAL_REF" in
refs/tags/*) : ;; # tag, ok
*) TAGS_ONLY=false ;;
esac
done < "$TMP_REFS"
fi
rm -f "$TMP_REFS" 2>/dev/null || true
# Log file for verbose output (keeps hook stdout small to avoid SIGPIPE)
PRE_PUSH_LOG=$(mktemp 2>/dev/null || echo ./.pre-push-log)
cleanup_log() { rm -f "$PRE_PUSH_LOG" 2>/dev/null || true; }
trap cleanup_log EXIT
# Run linting first to catch syntax and style issues
echo "π Running ESLint to catch linting issues..."
if ! pnpm run lint > "$PRE_PUSH_LOG" 2>&1; then
echo ""
echo "β Lint failed! Push blocked to prevent broken code on GitHub."
echo "π‘ Run 'pnpm run lint' to see all errors, or 'pnpm run lint:fix' for auto-fixes."
echo ""
tail -20 "$PRE_PUSH_LOG"
exit 1
fi
echo "β
Lint passed!"
echo ""
# Clean build to ensure no stale artifacts
echo "π¨ Running clean build to ensure fresh compilation..."
if ! (npm run clean > "$PRE_PUSH_LOG" 2>&1 && npm run build >> "$PRE_PUSH_LOG" 2>&1); then
echo ""
echo "β Build failed! Push blocked to prevent broken code on GitHub."
echo "π‘ Fix build errors before pushing."
echo ""
tail -20 "$PRE_PUSH_LOG"
exit 1
fi
echo "β
Build successful!"
echo ""
# Run tests (reduced suite for tag-only pushes to match release CI)
echo "π§ͺ Running tests (this may take a few minutes)..."
if [ "$TAGS_ONLY" = true ]; then
echo "π Tag-only push detected. Running reduced CI tests..."
npm run test:ci-no-python > "$PRE_PUSH_LOG" 2>&1
else
npm test > "$PRE_PUSH_LOG" 2>&1
fi
TEST_EXIT=$?
# Show the test summary (last few lines contain pass/fail counts)
grep -E "Test Files|Tests |Duration" "$PRE_PUSH_LOG" || true
if [ $TEST_EXIT -eq 0 ]; then
echo ""
echo "β
All tests passed! Push will proceed."
echo "π Code quality maintained for GitHub repository."
else
echo ""
echo "β Tests failed! Push blocked to prevent broken code on GitHub."
echo "π‘ Fix failing tests or use 'git push --no-verify' for emergency pushes."
echo "π§ You can still make local commits for WIP - tests only run on push."
echo ""
echo "Failed test details:"
grep -E "FAIL|Error|AssertionError" "$PRE_PUSH_LOG" | head -20 || true
echo ""
echo "Full log: $PRE_PUSH_LOG"
# Keep log on failure so user can inspect
trap - EXIT
exit 1
fi
# Docstar shadow documentation check (optional β runs only if docstar is installed)
if command -v docstar >/dev/null 2>&1; then
echo "π Docstar: Checking shadow documentation..."
docstar check || true
fi