#!/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 }