#!/usr/bin/env bash set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" BINARY="$PROJECT_ROOT/target/debug/openshell" STATE_FILE="$PROJECT_ROOT/.cache/openshell-build.state" # --------------------------------------------------------------------------- # Fingerprint-based rebuild check # # Mirrors the approach in tasks/scripts/cluster-deploy-fast.sh: collect dirty # files from git, filter to paths in the navigator-cli dependency closure, # hash their contents, and compare against a persisted state file. We also # track HEAD so that branch switches / pulls are detected. # --------------------------------------------------------------------------- needs_build=0 if [[ ! -x "$BINARY" ]]; then needs_build=1 else cd "$PROJECT_ROOT" # Current HEAD commit (detects branch switches, pulls, rebases) current_head=$(git rev-parse HEAD 2>/dev/null || echo "unknown") # Collect dirty (modified, staged, untracked) files mapfile -t changed_files < <( { git diff --name-only 2>/dev/null git diff --name-only --cached 2>/dev/null git ls-files --others --exclude-standard 2>/dev/null } | sort -u ) # Filter to paths that can affect the navigator-cli binary matches_cli() { local path=$1 case "$path" in Cargo.toml|Cargo.lock|proto/*) return 0 ;; crates/navigator-cli/*|crates/navigator-core/*|crates/navigator-bootstrap/*) return 0 ;; crates/navigator-policy/*|crates/navigator-providers/*|crates/navigator-tui/*) return 0 ;; *) return 1 ;; esac } # Compute a composite fingerprint of matching dirty files payload="" for path in "${changed_files[@]}"; do matches_cli "$path" || continue if [[ -e "$path" ]]; then digest=$(shasum -a 256 "$path" | cut -d ' ' -f 1) else digest="__MISSING__" fi payload+="${path}:${digest}"$'\n' done if [[ -n "$payload" ]]; then current_fingerprint=$(printf '%s' "$payload" | shasum -a 256 | cut -d ' ' -f 1) else current_fingerprint="" fi # Load previous state previous_head="" previous_fingerprint="" if [[ -f "$STATE_FILE" ]]; then while IFS='=' read -r key value; do case "$key" in head) previous_head=$value ;; fingerprint) previous_fingerprint=$value ;; esac done < "$STATE_FILE" fi # Decide whether to rebuild if [[ "$current_head" != "$previous_head" ]]; then needs_build=1 elif [[ "$current_fingerprint" != "$previous_fingerprint" ]]; then needs_build=1 fi fi if [[ "$needs_build" == "1" ]]; then echo "Recompiling openshell..." >&2 cargo build --package navigator-cli --quiet # Persist state after successful build mkdir -p "$(dirname "$STATE_FILE")" cd "$PROJECT_ROOT" new_head=$(git rev-parse HEAD 2>/dev/null || echo "unknown") # Recompute fingerprint of remaining dirty files (build may not change them) mapfile -t post_files < <( { git diff --name-only 2>/dev/null git diff --name-only --cached 2>/dev/null git ls-files --others --exclude-standard 2>/dev/null } | sort -u ) matches_cli() { local path=$1 case "$path" in Cargo.toml|Cargo.lock|proto/*) return 0 ;; crates/navigator-cli/*|crates/navigator-core/*|crates/navigator-bootstrap/*) return 0 ;; crates/navigator-policy/*|crates/navigator-providers/*|crates/navigator-tui/*) return 0 ;; *) return 1 ;; esac } post_payload="" for path in "${post_files[@]}"; do matches_cli "$path" || continue if [[ -e "$path" ]]; then digest=$(shasum -a 256 "$path" | cut -d ' ' -f 1) else digest="__MISSING__" fi post_payload+="${path}:${digest}"$'\n' done if [[ -n "$post_payload" ]]; then new_fingerprint=$(printf '%s' "$post_payload" | shasum -a 256 | cut -d ' ' -f 1) else new_fingerprint="" fi cat > "$STATE_FILE" <