#!/bin/zsh
# ╔═══════════════════════════════════════════════════════════════════╗
# ║ ws-check: Build & test only the npm workspaces with dirty files ║
# ║ Usage: wscheck [-b [base_branch]] ║
# ║ ║
# ║ Flags: ║
# ║ -b, --branch [base] Check all files changed on the current ║
# ║ branch vs base (default: main or master) ║
# ║ ║
# ║ 🤖 Written by a human + AI pair programming sesh (GitHub ║
# ║ Copilot CLI, Claude). Don't trust it blindly — read the ║
# ║ code, break the code, own the code. Down with black boxes. ║
# ║ ║
# ║ "The street finds its own uses for things." — William Gibson ║
# ╚═══════════════════════════════════════════════════════════════════╝
wscheck() {
local branch_mode=0
local base_branch=""
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
-b|--branch)
branch_mode=1
if [[ -n "$2" && "$2" != -* ]]; then
base_branch="$2"
shift
fi
shift
;;
*)
echo "\033[31m✗ Unknown argument: $1\033[0m"
echo "Usage: wscheck [-b|--branch [base_branch]]"
return 1
;;
esac
done
local root=$(git rev-parse --show-toplevel 2>/dev/null)
if [[ $? -ne 0 ]]; then
echo "\033[31m✗ Not in a git repository\033[0m"
return 1
fi
if [[ ! -f "$root/package.json" ]]; then
echo "\033[31m✗ No package.json found at repo root\033[0m"
return 1
fi
# Get workspace patterns from root package.json
local ws_patterns=("${(@f)$(jq -r '.workspaces // .workspaces.packages // [] | .[]' "$root/package.json" 2>/dev/null)}")
if [[ ${#ws_patterns[@]} -eq 0 ]]; then
echo "\033[31m✗ No workspaces defined in package.json\033[0m"
return 1
fi
# Get changed files (branch mode or dirty mode)
local changed_files=()
if [[ $branch_mode -eq 1 ]]; then
# Auto-detect default branch if not specified
if [[ -z "$base_branch" ]]; then
if git -C "$root" show-ref --verify --quiet refs/heads/main; then
base_branch="main"
elif git -C "$root" show-ref --verify --quiet refs/heads/master; then
base_branch="master"
else
echo "\033[31m✗ Could not detect base branch (no main or master found). Specify one: wscheck -b \033[0m"
return 1
fi
fi
local merge_base=$(git -C "$root" merge-base "$base_branch" HEAD 2>/dev/null)
if [[ $? -ne 0 ]]; then
echo "\033[31m✗ Could not find merge-base with '$base_branch' — does that branch exist?\033[0m"
return 1
fi
changed_files=("${(@f)$(git -C "$root" diff --name-only "$merge_base"..HEAD)}")
echo "\033[2m🌿 Branch mode: comparing against $base_branch ($(echo $merge_base | cut -c1-8))\033[0m"
echo ""
else
# Staged + unstaged + untracked
changed_files=("${(@f)$(git -C "$root" status --porcelain | awk '{print $NF}')}")
fi
if [[ ${#changed_files[@]} -eq 0 || -z "${changed_files[1]}" ]]; then
echo "\033[32m✓ No changed files — nothing to do 🎉\033[0m"
return 0
fi
# Resolve workspace directories from glob patterns
local ws_dirs=()
for pattern in $ws_patterns; do
for dir in $root/$~pattern(/N); do
if [[ -f "$dir/package.json" ]]; then
ws_dirs+=("${dir#$root/}")
fi
done
done
# Find which workspaces have changes
local affected_ws=()
for ws in $ws_dirs; do
for file in $changed_files; do
if [[ "$file" == "$ws"/* ]]; then
affected_ws+=("$ws")
break
fi
done
done
if [[ ${#affected_ws[@]} -eq 0 ]]; then
echo "\033[32m✓ Changes don't affect any workspace — all good 👍\033[0m"
return 0
fi
echo "\033[1;36m⚡ Affected workspaces:\033[0m"
for ws in $affected_ws; do
local ws_name=$(jq -r '.name // "unnamed"' "$root/$ws/package.json")
echo " \033[33m◆\033[0m $ws_name \033[2m($ws)\033[0m"
done
echo ""
# Spinner frames
local spin_frames=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
local failures=()
local successes=()
local total=${#affected_ws[@]}
local current=0
_spin_run() {
local label="$1"
local cmd="$2"
local logfile=$(mktemp)
local pid frame_idx
eval "$cmd" > "$logfile" 2>&1 &
pid=$!
frame_idx=0
while kill -0 $pid 2>/dev/null; do
printf "\r ${spin_frames[$((frame_idx % ${#spin_frames[@]} + 1))]} \033[1m%s\033[0m" "$label"
frame_idx=$((frame_idx + 1))
sleep 0.08
done
wait $pid
local exit_code=$?
if [[ $exit_code -eq 0 ]]; then
printf "\r \033[32m✓\033[0m %s\033[K\n" "$label"
else
printf "\r \033[31m✗\033[0m %s\033[K\n" "$label"
fi
# Return log content via global var (zsh workaround)
_spin_log=$(cat "$logfile")
rm -f "$logfile"
return $exit_code
}
for ws in $affected_ws; do
current=$((current + 1))
local ws_name=$(jq -r '.name // "unnamed"' "$root/$ws/package.json")
local has_build=$(jq -r '.scripts.build // empty' "$root/$ws/package.json")
local has_test=$(jq -r '.scripts.test // empty' "$root/$ws/package.json")
echo "\033[1m[$current/$total]\033[0m \033[1;35m$ws_name\033[0m"
# Build
if [[ -n "$has_build" ]]; then
_spin_run "build" "cd '$root/$ws' && npm run build"
if [[ $? -ne 0 ]]; then
failures+=("\033[31m✗ $ws_name → build\033[0m\n\033[2m$_spin_log\033[0m")
echo ""
continue
fi
else
echo " \033[2m⊘ no build script, skipping\033[0m"
fi
# Test
if [[ -n "$has_test" ]]; then
_spin_run "test" "cd '$root/$ws' && npm test"
if [[ $? -ne 0 ]]; then
failures+=("\033[31m✗ $ws_name → test\033[0m\n\033[2m$_spin_log\033[0m")
echo ""
continue
fi
else
echo " \033[2m⊘ no test script, skipping\033[0m"
fi
successes+=("$ws_name")
echo ""
done
# Summary
echo "\033[1m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m"
if [[ ${#successes[@]} -gt 0 ]]; then
echo "\033[32m✓ Passed: ${#successes[@]}/$total\033[0m"
fi
if [[ ${#failures[@]} -gt 0 ]]; then
echo "\033[31m✗ Failed: ${#failures[@]}/$total\033[0m"
echo ""
echo "\033[1;31m╭─ Failures ─────────────────────────────╮\033[0m"
for fail in $failures; do
echo ""
echo -e " $fail"
done
echo ""
echo "\033[1;31m╰────────────────────────────────────────╯\033[0m"
return 1
else
echo "\033[32m\n🎉 All workspaces passed! Ship it! 🚀\033[0m"
return 0
fi
}