Running a dozen Next.js devservers without losing your mind

Right now it seems to me the best way to work is with a bunch of Claude Code instances via Conductor. I’m getting out north of 30 PRs a day this way, some of them very significant in scope. What this means, though, is a lot of context switching. I am not going to remember that “fix-card-image-bg-loading” is running on port 3002 and “test-dropbox-cache-bust” is running on port 3010. So Claude and I wrote a shell script to keep us sane.

The basic idea is that you should be able to refer to any branch you are working on, by its branch name, and the script translates that into a running devserver and a browser pointing at it and local Inngest, too, isolated to just one devserver, if you need that. I also generate zsh completions so I can tab complete the branch name in all these commands.

#!/bin/bash
#
# mydev - Manage devservers across workspaces
#
# Usage:
#   mydev [workspace]               Start devserver and open browser (default)
#   mydev open [workspace]          Same as above (explicit)
#   mydev start [workspace]         Start devserver if not running (no browser, idempotent)
#   mydev cd [workspace]            cd to workspace/repo root
#   mydev edit [workspace]          Open workspace in editor ($VISUAL or $EDITOR)
#   mydev list                      List all running devservers and Inngest status
#   mydev inngest [workspace]       Start Inngest dev server for a specific workspace
#   mydev inngest-open              Open Inngest dashboard in browser
#   mydev stop [workspace]          Stop devserver for workspace
#   mydev stop-all                  Stop all devservers
#   mydev help                      Show this help
#
# Workspace can be specified as:
#   - Workspace name (e.g., "feature-1", "bugfix-2")
#   - Branch name (e.g., "add-user-auth") - finds matching workspace
#   - "main" - the main repo checkout at ~/my-project
#   - Omitted - detects from current directory or git branch
#
# Inngest:
#   The Inngest dev server connects to ONE app instance at a time.
#   This prevents confusion when multiple instances are running - only the
#   connected one will handle Inngest jobs.
#
#   To use Inngest:
#     1. Start your devserver: mydev my-branch
#     2. In another terminal, connect Inngest: mydev inngest my-branch
#     3. To switch to a different instance: mydev inngest other-branch
#
#   Dashboard: http://localhost:8288
#
# Setup:
#   Add to your ~/.zshrc:
#
#     export PATH="$HOME/my-project/scripts:$PATH"
#     source "$HOME/my-project/scripts/mydev-completion.zsh"
#
#   This gives you:
#     - mydev command available everywhere
#     - Tab completion for workspace names, branch names, and commands

set -e

REGISTRY_FILE="$HOME/.mydev-devservers.json"
INNGEST_LOCK_FILE="$HOME/.mydev-inngest-lock"
WORKSPACES_DIR="$HOME/workspaces"
MAIN_REPO="$HOME/my-project"
MAIN_WORKSPACE_NAME="main"
APP_SUBDIR="apps/frontend"  # Path to your app within the repo
MIN_PORT=3000
MAX_PORT=3020
INNGEST_PORT=8288

# Initialize registry file if it doesn't exist
init_registry() {
  if [[ ! -f "$REGISTRY_FILE" ]]; then
    echo '{}' > "$REGISTRY_FILE"
  fi
}

# Find workspace by branch name (scans all workspaces including main repo)
find_workspace_by_branch() {
  local target_branch="$1"

  # Check main repo first
  if [[ -d "$MAIN_REPO/.git" ]]; then
    local branch=$(git -C "$MAIN_REPO" rev-parse --abbrev-ref HEAD 2>/dev/null)
    if [[ "$branch" == "$target_branch" ]]; then
      echo "$MAIN_WORKSPACE_NAME"
      return
    fi
  fi

  # Check workspaces
  for workspace_dir in "$WORKSPACES_DIR"/*/; do
    if [[ -d "$workspace_dir/.git" ]] || [[ -f "$workspace_dir/.git" ]]; then
      local branch=$(git -C "$workspace_dir" rev-parse --abbrev-ref HEAD 2>/dev/null)
      if [[ "$branch" == "$target_branch" ]]; then
        basename "$workspace_dir"
        return
      fi
    fi
  done

  echo ""
}

# Get workspace name from current directory or argument
get_workspace() {
  local input="$1"

  if [[ -n "$input" ]]; then
    if [[ "$input" == "$MAIN_WORKSPACE_NAME" ]]; then
      echo "$MAIN_WORKSPACE_NAME"
      return
    fi

    if [[ -d "$WORKSPACES_DIR/$input" ]]; then
      echo "$input"
      return
    fi

    local ws=$(find_workspace_by_branch "$input")
    if [[ -n "$ws" ]]; then
      echo "$ws"
      return
    fi

    echo ""
    return
  fi

  # Try to detect from current directory
  local cwd="$(pwd)"
  if [[ "$cwd" == "$MAIN_REPO"* ]]; then
    echo "$MAIN_WORKSPACE_NAME"
    return
  fi

  if [[ "$cwd" == "$WORKSPACES_DIR/"* ]]; then
    local rel_path="${cwd#$WORKSPACES_DIR/}"
    local workspace="${rel_path%%/*}"
    echo "$workspace"
    return
  fi

  # Fallback: try to match current git branch to a workspace's branch
  if command -v git &>/dev/null && git rev-parse --git-dir &>/dev/null; then
    local current_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
    if [[ -n "$current_branch" ]]; then
      local ws=$(find_workspace_by_branch "$current_branch")
      if [[ -n "$ws" ]]; then
        echo "$ws"
        return
      fi
    fi
  fi

  echo ""
}

# Get app directory for a workspace
get_app_dir() {
  local workspace="$1"

  if [[ "$workspace" == "$MAIN_WORKSPACE_NAME" ]]; then
    echo "$MAIN_REPO/$APP_SUBDIR"
  else
    echo "$WORKSPACES_DIR/$workspace/$APP_SUBDIR"
  fi
}

# Get workspace root directory
get_workspace_root() {
  local workspace="$1"

  if [[ "$workspace" == "$MAIN_WORKSPACE_NAME" ]]; then
    echo "$MAIN_REPO"
  else
    echo "$WORKSPACES_DIR/$workspace"
  fi
}

# Validate workspace exists and has the app
validate_workspace() {
  local workspace="$1"
  local app_path=$(get_app_dir "$workspace")

  if [[ ! -d "$app_path" ]]; then
    echo "Error: App not found at $app_path" >&2
    echo "" >&2
    echo "Available workspaces:" >&2

    if [[ -d "$MAIN_REPO/$APP_SUBDIR" ]]; then
      local main_branch=$(git -C "$MAIN_REPO" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
      echo "  $main_branch ($MAIN_WORKSPACE_NAME) [main repo]" >&2
    fi

    for ws_dir in "$WORKSPACES_DIR"/*/; do
      local ws_name=$(basename "$ws_dir")
      local ws_branch=$(git -C "$ws_dir" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
      echo "  $ws_branch ($ws_name)" >&2
    done
    return 1
  fi
  return 0
}

# Check if a port is in use
is_port_in_use() {
  local port="$1"
  lsof -i ":$port" >/dev/null 2>&1
}

# Find the first available port
find_available_port() {
  for port in $(seq $MIN_PORT $MAX_PORT); do
    if ! is_port_in_use "$port"; then
      echo "$port"
      return
    fi
  done
  echo ""
}

# Get PID of process using a port
get_pid_on_port() {
  local port="$1"
  lsof -ti ":$port" 2>/dev/null | head -1
}

# Find existing devserver for a workspace by scanning processes
find_existing_devserver() {
  local workspace="$1"
  local workspace_root=$(get_workspace_root "$workspace")

  local search_pattern
  if [[ "$workspace" == "$MAIN_WORKSPACE_NAME" ]]; then
    search_pattern="$MAIN_REPO/$APP_SUBDIR"
  else
    search_pattern="$workspace/$APP_SUBDIR"
  fi

  local process_line=$(ps aux | grep -E "$search_pattern.*/next.*dev|next.*dev.*$search_pattern" | grep -v grep | head -1)

  if [[ -z "$process_line" ]]; then
    process_line=$(ps aux | grep -E "$search_pattern.*next-server|next-server.*$search_pattern" | grep -v grep | head -1)
  fi

  if [[ -n "$process_line" ]]; then
    local pid=$(echo "$process_line" | awk '{print $2}')
    local port=$(echo "$process_line" | grep -oE '\-p [0-9]+' | tail -1 | awk '{print $2}')

    if [[ -z "$port" ]]; then
      port=3000
    fi

    if [[ -n "$pid" ]] && [[ -n "$port" ]]; then
      echo "$port:$pid"
      return
    fi
  fi

  echo ""
}

# Registry operations
get_workspace_port() {
  local workspace="$1"
  if [[ -f "$REGISTRY_FILE" ]]; then
    jq -r ".[\"$workspace\"].port // empty" "$REGISTRY_FILE"
  fi
}

get_workspace_pid() {
  local workspace="$1"
  if [[ -f "$REGISTRY_FILE" ]]; then
    jq -r ".[\"$workspace\"].pid // empty" "$REGISTRY_FILE"
  fi
}

is_workspace_running() {
  local workspace="$1"
  local port=$(get_workspace_port "$workspace")

  if [[ -z "$port" ]]; then
    return 1
  fi

  if is_port_in_use "$port"; then
    return 0
  fi

  return 1
}

save_registry_entry() {
  local workspace="$1"
  local port="$2"
  local pid="$3"

  local tmp_file=$(mktemp)
  jq --arg ws "$workspace" --arg port "$port" --arg pid "$pid" \
    '.[$ws] = {port: ($port | tonumber), pid: ($pid | tonumber)}' \
    "$REGISTRY_FILE" > "$tmp_file" && mv "$tmp_file" "$REGISTRY_FILE"
}

remove_registry_entry() {
  local workspace="$1"

  local tmp_file=$(mktemp)
  jq --arg ws "$workspace" 'del(.[$ws])' "$REGISTRY_FILE" > "$tmp_file" && mv "$tmp_file" "$REGISTRY_FILE"
}

cleanup_registry() {
  if [[ ! -f "$REGISTRY_FILE" ]]; then
    return
  fi

  local workspaces=$(jq -r 'keys[]' "$REGISTRY_FILE")
  for ws in $workspaces; do
    if ! is_workspace_running "$ws"; then
      remove_registry_entry "$ws"
    fi
  done
}

# Inngest operations
is_inngest_running() {
  is_port_in_use "$INNGEST_PORT"
}

get_inngest_lock_workspace() {
  if [[ -f "$INNGEST_LOCK_FILE" ]]; then
    cat "$INNGEST_LOCK_FILE"
  else
    echo ""
  fi
}

set_inngest_lock() {
  local workspace="$1"
  echo "$workspace" > "$INNGEST_LOCK_FILE"
}

clear_inngest_lock() {
  rm -f "$INNGEST_LOCK_FILE"
}

is_inngest_lock_valid() {
  if is_inngest_running && [[ -f "$INNGEST_LOCK_FILE" ]]; then
    return 0
  fi
  if [[ -f "$INNGEST_LOCK_FILE" ]] && ! is_inngest_running; then
    clear_inngest_lock
  fi
  return 1
}

stop_inngest_and_wait() {
  local inngest_pid=$(get_pid_on_port "$INNGEST_PORT")
  if [[ -n "$inngest_pid" ]]; then
    kill "$inngest_pid" 2>/dev/null || true
  fi

  local attempts=0
  local max_attempts=50
  while is_port_in_use "$INNGEST_PORT"; do
    sleep 0.1
    ((attempts++))
    if [[ $attempts -ge $max_attempts ]]; then
      echo "Warning: Inngest port $INNGEST_PORT still in use after 5s, forcing..." >&2
      lsof -ti ":$INNGEST_PORT" | xargs kill -9 2>/dev/null || true
      sleep 0.5
      break
    fi
  done

  clear_inngest_lock
}

# Start devserver for a workspace
start_devserver() {
  local workspace="$1"
  local no_browser="${2:-}"
  local app_path=$(get_app_dir "$workspace")
  local ws_root=$(get_workspace_root "$workspace")

  validate_workspace "$workspace" || exit 1

  local port=$(find_available_port)
  if [[ -z "$port" ]]; then
    echo "Error: No available ports between $MIN_PORT and $MAX_PORT" >&2
    exit 1
  fi

  local branch=$(git -C "$ws_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "$workspace")

  echo -ne "\033]0;▶ [$branch $port]\007"
  if is_inngest_running; then
    echo "🚀 App: $branch (port $port, inngest: $INNGEST_PORT)"
  else
    echo "🚀 App: $branch (port $port)"
    echo "   Note: Inngest not running. Start with: mydev inngest"
  fi

  cd "$app_path"

  NEXT_PUBLIC_APP_URL="http://localhost:$port" INNGEST_BASE_URL="http://localhost:$INNGEST_PORT" pnpm dev -p "$port" &
  local dev_pid=$!

  trap 'kill $dev_pid 2>/dev/null; exit' INT TERM
  trap 'kill $dev_pid 2>/dev/null' EXIT

  local attempts=0
  local max_attempts=150
  while ! lsof -i ":$port" >/dev/null 2>&1; do
    if ! kill -0 "$dev_pid" 2>/dev/null; then
      echo "Error: devserver failed to start" >&2
      wait "$dev_pid"
      exit 1
    fi
    sleep 0.2
    ((attempts++))
    if [[ $attempts -ge $max_attempts ]]; then
      echo "Warning: Server not detected on port $port yet" >&2
      break
    fi
  done

  if [[ "$no_browser" != "--no-browser" ]]; then
    open "http://localhost:$port"
  fi

  wait "$dev_pid"
}

# Stop devserver for a workspace
stop_devserver() {
  local workspace="$1"

  local port=$(get_workspace_port "$workspace")
  local pid=$(get_workspace_pid "$workspace")

  if [[ -z "$pid" ]]; then
    local existing=$(find_existing_devserver "$workspace")
    if [[ -n "$existing" ]]; then
      port="${existing%%:*}"
      pid="${existing##*:}"
    fi
  fi

  if [[ -z "$pid" ]]; then
    echo "No devserver found for workspace '$workspace'"
    return
  fi

  echo "Stopping devserver for '$workspace' (PID: $pid)..."

  pkill -P "$pid" 2>/dev/null || true
  kill "$pid" 2>/dev/null || true
  pkill -f "$workspace/$APP_SUBDIR.*next" 2>/dev/null || true

  remove_registry_entry "$workspace"
  echo "Stopped."
}

# Stop all devservers
stop_all_devservers() {
  cleanup_registry

  local stopped_any=false

  if [[ -f "$REGISTRY_FILE" ]] && [[ "$(jq 'length' "$REGISTRY_FILE")" != "0" ]]; then
    local workspaces=$(jq -r 'keys[]' "$REGISTRY_FILE")
    for ws in $workspaces; do
      stop_devserver "$ws"
      stopped_any=true
    done
  fi

  if [[ -d "$MAIN_REPO/$APP_SUBDIR" ]]; then
    local existing=$(find_existing_devserver "$MAIN_WORKSPACE_NAME")
    if [[ -n "$existing" ]]; then
      stop_devserver "$MAIN_WORKSPACE_NAME"
      stopped_any=true
    fi
  fi

  for workspace_dir in "$WORKSPACES_DIR"/*/; do
    local ws=$(basename "$workspace_dir")
    local existing=$(find_existing_devserver "$ws")
    if [[ -n "$existing" ]]; then
      stop_devserver "$ws"
      stopped_any=true
    fi
  done

  if [[ "$stopped_any" != "true" ]]; then
    echo "No devservers running."
  fi
}

# List all running devservers
list_devservers() {
  cleanup_registry

  local inngest_lock_ws=""
  if is_inngest_lock_valid; then
    inngest_lock_ws=$(get_inngest_lock_workspace)
  fi

  echo "Inngest:"
  if is_inngest_running; then
    if [[ -n "$inngest_lock_ws" ]]; then
      local lock_ws_root=$(get_workspace_root "$inngest_lock_ws")
      local lock_branch=$(git -C "$lock_ws_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "$inngest_lock_ws")
      echo "  ✅ Running on port $INNGEST_PORT → $lock_branch ($inngest_lock_ws)"
    else
      echo "  ⚠️  Running on port $INNGEST_PORT (no lock - may route unpredictably)"
    fi
  else
    echo "  ❌ Not running. Start with: mydev inngest [workspace]"
  fi
  echo ""

  echo "Devservers:"
  echo ""
  printf "%-30s %-20s %-6s %-10s\n" "BRANCH" "(WORKSPACE)" "PORT" "INNGEST"
  printf "%-30s %-20s %-6s %-10s\n" "------" "-----------" "----" "-------"

  local found_any=false

  if [[ -f "$REGISTRY_FILE" ]] && [[ "$(jq 'length' "$REGISTRY_FILE")" != "0" ]]; then
    jq -r 'to_entries[] | "\(.key)\t\(.value.port)\t\(.value.pid)"' "$REGISTRY_FILE" | \
      while IFS=$'\t' read -r ws port pid; do
        local ws_root=$(get_workspace_root "$ws")
        local branch=$(git -C "$ws_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "?")
        branch="${branch:0:30}"
        local inngest_marker=""
        if [[ "$ws" == "$inngest_lock_ws" ]]; then
          inngest_marker="← connected"
        fi
        printf "%-30s %-20s %-6s %-10s\n" "$branch" "($ws)" "$port" "$inngest_marker"
      done
    found_any=true
  fi

  if [[ -d "$MAIN_REPO/$APP_SUBDIR" ]]; then
    if [[ ! -f "$REGISTRY_FILE" ]] || ! jq -e ".[\"$MAIN_WORKSPACE_NAME\"]" "$REGISTRY_FILE" >/dev/null 2>&1; then
      local existing=$(find_existing_devserver "$MAIN_WORKSPACE_NAME")
      if [[ -n "$existing" ]]; then
        local port="${existing%%:*}"
        local branch=$(git -C "$MAIN_REPO" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "?")
        branch="${branch:0:30}"
        local inngest_marker=""
        if [[ "$MAIN_WORKSPACE_NAME" == "$inngest_lock_ws" ]]; then
          inngest_marker="← connected"
        fi
        printf "%-30s %-20s %-6s %-10s\n" "$branch" "($MAIN_WORKSPACE_NAME)" "$port" "$inngest_marker"
        found_any=true
      fi
    fi
  fi

  for workspace_dir in "$WORKSPACES_DIR"/*/; do
    local ws=$(basename "$workspace_dir")
    if [[ -f "$REGISTRY_FILE" ]] && jq -e ".[\"$ws\"]" "$REGISTRY_FILE" >/dev/null 2>&1; then
      continue
    fi
    local existing=$(find_existing_devserver "$ws")
    if [[ -n "$existing" ]]; then
      local port="${existing%%:*}"
      local branch=$(git -C "$workspace_dir" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "?")
      branch="${branch:0:30}"
      local inngest_marker=""
      if [[ "$ws" == "$inngest_lock_ws" ]]; then
        inngest_marker="← connected"
      fi
      printf "%-30s %-20s %-6s %-10s\n" "$branch" "($ws)" "$port" "$inngest_marker"
      found_any=true
    fi
  done

  if [[ "$found_any" != "true" ]]; then
    echo "(none)"
  fi
}

# Commands
cmd_start() {
  local input="${1:-}"
  local workspace=$(get_workspace "$input")

  if [[ -z "$workspace" ]]; then
    if [[ -n "$input" ]]; then
      echo "Error: No workspace or branch found matching '$input'" >&2
    else
      echo "Error: No workspace specified and could not detect from current directory" >&2
    fi
    exit 1
  fi

  if is_workspace_running "$workspace"; then
    local port=$(get_workspace_port "$workspace")
    echo "Devserver for '$workspace' is already running on port $port" >&2
    echo "$port"
    exit 0
  fi

  local existing=$(find_existing_devserver "$workspace")
  if [[ -n "$existing" ]]; then
    local port="${existing%%:*}"
    local pid="${existing##*:}"
    echo "Found existing devserver for '$workspace' on port $port (PID: $pid)" >&2
    save_registry_entry "$workspace" "$port" "$pid"
    echo "$port"
    exit 0
  fi

  start_devserver "$workspace" --no-browser
}

cmd_open() {
  local input="${1:-}"
  local workspace=$(get_workspace "$input")

  if [[ -z "$workspace" ]]; then
    cmd_start "$input"
    return
  fi

  if is_workspace_running "$workspace"; then
    local port=$(get_workspace_port "$workspace")
    echo "Devserver for '$workspace' is already running on port $port"
    open "http://localhost:$port"
    exit 0
  fi

  local existing=$(find_existing_devserver "$workspace")
  if [[ -n "$existing" ]]; then
    local port="${existing%%:*}"
    local pid="${existing##*:}"
    echo "Found existing devserver for '$workspace' on port $port (PID: $pid)"
    save_registry_entry "$workspace" "$port" "$pid"
    open "http://localhost:$port"
    exit 0
  fi

  start_devserver "$workspace"
}

cmd_dir() {
  local workspace=$(get_workspace "${1:-}")

  if [[ -z "$workspace" ]]; then
    echo "Error: No workspace specified and could not detect from current directory" >&2
    exit 1
  fi

  local ws_root=$(get_workspace_root "$workspace")

  if [[ ! -d "$ws_root" ]]; then
    echo "Error: Workspace root not found at $ws_root" >&2
    exit 1
  fi

  echo "$ws_root"
}

cmd_edit() {
  local workspace=$(get_workspace "${1:-}")

  if [[ -z "$workspace" ]]; then
    echo "Error: No workspace specified and could not detect from current directory" >&2
    exit 1
  fi

  local ws_root=$(get_workspace_root "$workspace")

  if [[ ! -d "$ws_root" ]]; then
    echo "Error: Workspace root not found at $ws_root" >&2
    exit 1
  fi

  local editor="${VISUAL:-$EDITOR}"

  if [[ -z "$editor" ]]; then
    echo "Error: No editor configured. Set \$VISUAL or \$EDITOR." >&2
    exit 1
  fi

  exec "$editor" "$ws_root"
}

cmd_inngest_open() {
  if ! is_inngest_running; then
    echo "Error: Inngest is not running" >&2
    echo "Start it with: mydev inngest" >&2
    exit 1
  fi

  local url="http://localhost:$INNGEST_PORT"
  echo "Opening Inngest dashboard: $url"
  open "$url"
}

cmd_inngest() {
  local input="${1:-}"
  local workspace=$(get_workspace "$input")

  if [[ -z "$workspace" ]]; then
    if [[ -n "$input" ]]; then
      echo "Error: No workspace or branch found matching '$input'" >&2
    else
      echo "Error: No workspace specified and could not detect from current directory" >&2
    fi
    echo "" >&2
    echo "Usage: mydev inngest [workspace]" >&2
    exit 1
  fi

  validate_workspace "$workspace" || exit 1

  local ws_root=$(get_workspace_root "$workspace")
  local branch=$(git -C "$ws_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "$workspace")

  if is_inngest_lock_valid; then
    local current_lock=$(get_inngest_lock_workspace)
    if [[ "$current_lock" == "$workspace" ]]; then
      echo "Inngest is already running for '$workspace'" >&2
      echo "Dashboard: http://localhost:$INNGEST_PORT" >&2
      exit 0
    else
      local current_ws_root=$(get_workspace_root "$current_lock")
      local current_branch=$(git -C "$current_ws_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "$current_lock")
      echo "Switching Inngest from '$current_branch' to '$branch'..."
      stop_inngest_and_wait
    fi
  elif is_inngest_running; then
    echo "Taking over orphaned Inngest server..."
    stop_inngest_and_wait
  fi

  local app_port=""

  if is_workspace_running "$workspace"; then
    app_port=$(get_workspace_port "$workspace")
  else
    local existing=$(find_existing_devserver "$workspace")
    if [[ -n "$existing" ]]; then
      app_port="${existing%%:*}"
    fi
  fi

  if [[ -z "$app_port" ]]; then
    echo "Error: Devserver for '$workspace' is not running" >&2
    echo "" >&2
    echo "Start it first with: mydev $workspace" >&2
    exit 1
  fi

  set_inngest_lock "$workspace"

  trap 'clear_inngest_lock' EXIT INT TERM

  local app_url="http://localhost:$app_port/api/inngest"

  echo "🚀 Inngest dev server on port $INNGEST_PORT"
  echo "   Dashboard: http://localhost:$INNGEST_PORT"
  echo "   Connected to: $branch (port $app_port)"
  echo ""

  npx inngest-cli@latest dev --port "$INNGEST_PORT" --no-discovery -u "$app_url"
}

show_help() {
  cat << 'EOF'
mydev - Manage devservers across workspaces

USAGE:
  mydev [command] [workspace|branch]

COMMANDS:
  open [workspace]       Start devserver and open browser (default if no command given)
  start [workspace]      Start devserver if not running (no browser, idempotent)
  cd [workspace]         cd to workspace/repo root
  edit [workspace]       Open workspace in editor ($VISUAL or $EDITOR)
  list                   List all running devservers and Inngest status
  inngest [workspace]    Start Inngest dev server for a specific workspace
  inngest-open           Open Inngest dashboard in browser
  stop [workspace]       Stop devserver for workspace
  stop-all               Stop all devservers
  help                   Show this help

WORKSPACE ARGUMENT:
  Can be specified as:
  - Workspace name: "feature-1", "bugfix-2", etc.
  - Branch name: "add-user-auth" - finds matching workspace
  - Omitted: detects from current directory or git branch

INNGEST:
  The Inngest dev server connects to ONE app instance at a time.
  This prevents confusion when multiple instances are running.

  Workflow:
    1. Start your app: mydev my-branch
    2. Connect Inngest: mydev inngest my-branch
    3. Switch Inngest:  mydev inngest other-branch

  Dashboard: http://localhost:8288

EXAMPLES:
  mydev feature-1              # Start/open devserver for feature-1 workspace
  mydev start feature-1        # Start devserver (no browser)
  mydev my-feature-branch      # Start/open by branch name
  mydev                        # Auto-detect from current dir/branch
  mydev list                   # Show all running servers and Inngest status
  mydev inngest feature-1      # Connect Inngest to feature-1's app
  mydev inngest-open           # Open Inngest dashboard
  mydev stop feature-1         # Stop feature-1's server
  mydev cd feature-1           # cd to feature-1 workspace root
  mydev edit feature-1         # Open feature-1 in $VISUAL or $EDITOR
EOF
}

main() {
  init_registry
  cleanup_registry

  local cmd="${1:-}"

  case "$cmd" in
    list|ls|-l|--list)
      list_devservers
      ;;
    stop)
      shift
      local workspace=$(get_workspace "${1:-}")
      if [[ -z "$workspace" ]]; then
        echo "Error: No workspace specified and could not detect from current directory"
        exit 1
      fi
      stop_devserver "$workspace"
      ;;
    stop-all|--stop-all)
      stop_all_devservers
      ;;
    cd|dir|path)
      shift
      cmd_dir "$@"
      ;;
    edit)
      shift
      cmd_edit "$@"
      ;;
    inngest)
      shift
      cmd_inngest "$@"
      ;;
    inngest-open)
      shift
      cmd_inngest_open "$@"
      ;;
    open)
      shift
      cmd_open "$@"
      ;;
    start)
      shift
      cmd_start "$@"
      ;;
    help|--help|-h)
      show_help
      ;;
    *)
      cmd_open "$cmd"
      ;;
  esac
}

main "$@"

Rebase a whole stack at once

You’re working on a series of PRs, one per branch, each one based upon the precediung one.

trunk -> my-feature-1 -> my-feature-2 -> my-feature-3 -> my-feature-4

Then you get a code review on my-feature-1 and you need to make a change. Now you need all the branches from my-feature-2 onwards to be rebased on the new my-feature-1 and for that to be reflected in your local.

$ git switch my-feature-4
$ git rebase --update-refs my-feature-1

--update-refs will save you having to go to each branch in turn and rebase.

Here’s an example git repo.

shadcn/ui v3/v4 and –destructive theme variables

In shadcn v3 there were two destructive vars in a theme, --destructive and --destructive-foreground.

Various shadcn components used these the right way, but several used them wrong, using the background color (--destructive) for text. They mostly got away with this because both were generally a shade of red. But on dark backgrounds, it looked wrong.

In shadcn v4 they supply only a single destructive color in the themes and in darker themes it’s a brighter, more-legible color. They then both use it as a text color on normal backgrounds, or use it as a background color and force white as a foreground color. This still isn’t perfect, but it’s better.

Psychopathic Git Shortcuts

if type brew &>/dev/null; then
    FPATH=$(brew --prefix)/share/zsh-completions:$FPATH
    autoload -Uz compinit
    compinit
  fi

#compdef gt
###-begin-gt-completions-###
#
# yargs command completion script
#
# Installation: gt completion >> ~/.zshrc
#    or gt completion >> ~/.zprofile on OSX.
#
_gt_yargs_completions()
{
  local reply
  local si=$IFS
  IFS=$'
' reply=($(COMP_CWORD="$((CURRENT-1))" COMP_LINE="$BUFFER" COMP_POINT="$CURSOR" gt --get-yargs-completions "${words[@]}"))
  IFS=$si
  _describe 'values' reply
}
compdef _gt_yargs_completions gt
###-end-gt-completions-###

# gh completions
fpath=($HOME/.zsh/completions $fpath)
autoload -Uz compinit
compinit

# Determine defaultBranch once per repo, store it if not set
get_default_branch() {
  local branch
  branch=$(git config --get bakert.defaultBranch 2>/dev/null)

  if [[ -z "$branch" ]]; then
    branch=$(git remote show origin 2>/dev/null | awk '/HEAD branch/ {print $NF}')
    branch=${branch:-master}

    git config --local bakert.defaultBranch "$branch"
    echo "Set bakert.defaultBranch to '$branch' in local git config" >&2
  fi

  echo "$branch"
}

# Perform an operation that might fail if the current branch is implicated in a way that won't fail
# Leaves you on default branch after if your current branch ceased to exist.
with_default_branch() {
  local current_branch=$(git rev-parse --abbrev-ref HEAD)
  local default_branch=$(get_default_branch)

  if [[ "$current_branch" != "$default_branch" ]]; then
    git switch "$default_branch"
  fi

  "$@"

  if [[ "$current_branch" != "$default_branch" ]] && git show-ref --verify --quiet "refs/heads/$current_branch"; then
    git switch "$current_branch"
  fi
}

## Functions not aliases to allow completions
ga()    { git add "$@"; }; compdef _git ga=git-add
gaa()   { git add . "$@"; }; compdef _git gaa=git-add
# Equivlent to `git add !$`
gal() {
  emulate -L zsh
  setopt noglob

  local cmd last
  cmd=$(fc -ln -1) || return 1
  local -a words
  words=(${(z)cmd})
  (( ${#words} )) || { echo "No history."; return 1; }

  last=${words[-1]}
  last=${(Q)last}       # remove surrounding quotes if any

  # Expand ~ / ~user without enabling globbing
  if [[ $last == "~"* ]]; then
    last=${~last}
  fi

  [[ -n $last ]] || { echo "No last arg."; return 1; }
  git add -- "$last"
}; compdef _git gal=git-add
gap()   { git add -p "$@"; }; compdef _git gap=git-add
gb()    { git branch "$@"; }; compdef _git gb=git-branch
gbd()   { with_default_branch git branch -D "$@"; }; compdef _git gbd=git-branch
gbl()   { git blame "$@"; }; compdef _git gbl=git-blame
gc()    { git commit "$@"; }; compdef _git gc=git-commit
gca()   { git commit --amend "$@"; }; compdef _git gca=git-commit
gcae()  { git commit --amend --no-edit "$@"; }; compdef _git gcae=git-commit
gcan()  { git commit --amend -n "$@"; }; compdef _git gcan=git-commit
gcane() { git commit --amend -n --no-edit "$@"; }; compdef _git gcane=git-commit
gclb()  { gpru && gded }; compdef _git gclb=git-branch
gclfd() { git clean -fd "$@" }; compdef _git gclfd=git-clean
gcp()   { git cherry-pick "$@" }; compdef _git gcp=git-cherry-pick
gcpa()  { git cherry-pick --abort "$@" }; compdef _git gcpa=git-cherry-pick
gcpc()  { git cherry-pick --continue "$@" }; compdef _git gcpa=git-cherry-pick
glc()   { git clone "$@"; }; compdef _git glc=git-clone
gcm()   { git commit -m "$@"; }; compdef _git gcm=git-commit
gcn()   { git commit -n "$@"; }; compdef _git gcn=git-commit
gco()   { git checkout "$@"; }; compdef _git gco=git-checkout
gd()    { git diff "$@"; }; compdef _git gd=git-diff
gdc()   { git diff --cached "$@"; }; compdef _git gdc=git-diff
gdd()   { git diff $(get_default_branch) "$@"; }; compdef _git gdd=git-diff
# Delete all local branches that don't have changes not already in default branch
gded() {
  local default_branch=$(get_default_branch)
  for branch in $(git for-each-ref --format='%(refname:short)' refs/heads/); do
    if [[ "$branch" != "$default_branch" ]]; then
      local count=$(git rev-list --count "$branch" --not "$default_branch")
      if [[ "$count" -eq 0 ]]; then
        echo "Deleting branch: $branch"
        git branch -D "$branch"
      fi
    fi
  done
}
gfo()   { git fetch origin $(get_default_branch):$(get_default_branch) "$@"; }; compdef _git gfo=git-fetch
# Make a gist, guessing exactly what you want a gist of based on state of repo
gg() {
local target=$1
  local desc=""
  local filename=""
  local url=""

  if [[ -z $target ]]; then
    if [[ -n $(git status --porcelain) ]]; then
      desc="Working copy diff"
      filename="working.diff"
      url=$((git diff HEAD && git ls-files --others --exclude-standard | xargs -I {} git diff /dev/null {}) | gh gist create -f "$filename" -d "$desc" - | tail -n1)
    else
      desc="Top commit diff (HEAD)"
      filename="head.diff"
      url=$(git show HEAD | gh gist create -f "$filename" -d "$desc" - | tail -n1)
    fi
  elif [[ $target == "develop" ]]; then
    desc="Diff from develop"
    filename="develop.diff"
    url=$(git diff develop...HEAD | gh gist create -f "$filename" -d "$desc" - | tail -n1)
  else
	desc="Diff of $target"
	filename="$target.diff"
	url=$(git diff "$target...HEAD" | gh gist create -f "$filename" -d "$desc" - | tail -n1)
  fi

  echo "$url"
  open "$url"
}; compdef _git gg=git-show
# Make a gist of the difference between working copy and default branch
ggd() { gg "$(get_default_branch)"; }; compdef _git ggd=git-show
gl()  { git log "$@"; }; compdef _git gl=git-log
# Pretty one-liner log
glp() {
  local git_args=()
  if [[ $1 =~ ^-[0-9]+$ ]] || [[ $1 =~ ^--max-count=[0-9]+$ ]] || [[ $1 =~ ^-n$ && $2 =~ ^[0-9]+$ ]]; then
    git_args=("$@")
  else
    git_args=("$@")
  fi
  git log --pretty="tformat:$FORMAT" "${git_args[@]}" |
  column -t -s '{' |
  less -XRS --quit-if-one-screen
}; compdef _git glp=git-log
gm()   { git mv "$@"; }; compdef _git gm=git-mv
gp()   { git pull "$@"; }; compdef _git gp=git-pull
gpf()  { git push --force-with-lease "$@"; }; compdef _git gpf=git-push
gpr()  { gh pr create "$@"; }; compdef _gh gpr=gh-pr
gprb()  { gh pr create -B "$@"; }; compdef _gh gprb=gh-pr
gprd()  { gh pr create -d "$@"; }; compdef _gh gprd=gh-pr
gprdb() { gh pr create -d -B "$@"; }; compdef _gh gprdb=gh-pr
# Remove local branches that aren't on remote any more
gpru() {
  cleanup_gone_branches() {
    git remote update origin --prune
    local gone_branches=$(git branch -vvv | grep gone | cut -d' ' -f3)
    if [[ -n "$gone_branches" ]]; then
      echo "$gone_branches" | xargs -I{} git branch -D '{}'
    fi
  }
  with_default_branch cleanup_gone_branches
}
gpso()  { git push --set-upstream origin "${@:-$(git branch --show-current)}"; }; compdef _git gpso=git-push
grb()   { git rebase "$@"; }; compdef _git grb=git-rebase
grba()  { git rebase --abort "$@"; }; compdef _git grba=git-rebase
grbc()  { git rebase --continue "$@"; }; compdef _git grbc=git-rebase
grbd()  { git rebase $(get_default_branch) "$@"; }; compdef _git grbd=git-rebase
grbdo() { gfo && git rebase $(get_default_branch) "$@"; }; compdef _git grbd=git-rebase
grbi()  { git rebase -i $(get_default_branch) "$@"; }; compdef _git grbi=git-rebase
grl()   { git reflog "$@"; }; compdef _git grl=git-reflog
grs()   { git reset "$@"; }; compdef _git grs=git-reset
grsh1() { git reset HEAD~1 "$@"; }; compdef _git grsh1=git-reset
grsh2() { git reset HEAD~2 "$@"; }; compdef _git grsh2=git-reset
grsh3() { git reset HEAD~3 "$@"; }; compdef _git grsh3=git-reset
grsh4() { git reset HEAD~4 "$@"; }; compdef _git grsh4=git-reset
grsh5() { git reset HEAD~5 "$@"; }; compdef _git grsh5=git-reset
grsh6() { git reset HEAD~6 "$@"; }; compdef _git grsh6=git-reset
grsh7() { git reset HEAD~7 "$@"; }; compdef _git grsh7=git-reset
grsh8() { git reset HEAD~8 "$@"; }; compdef _git grsh8=git-reset
grsh9() { git reset HEAD~8 "$@"; }; compdef _git grsh9=git-reset
grt()   { git restore "$@"; }; compdef _git grt=git-restore
grta()  { git restore . "$@"; }; compdef _git grt=git-restore
grts()  { git restore --staged "$@"; }; compdef _git grt=git-restore
grtsa() { git restore --staged . "$@"; }; compdef _git grt=git-restore
grm()   { git rm "$@"; }; compdef _git grm=git-rm
gs()    { git status "$@"; }; compdef _git gs=git-status
gsh()   { git show "$@"; }; compdef _git gsh=git-show
gshn()  { git show --name-only "$@"; }; compdef _git gsh=git-show
gst()   { git stash "$@"; }; compdef _git gst=git-stash
gstd()  { git stash drop "$@"; }; compdef _git gstd=git-stash
gstl()  { git stash list "$@"; }; compdef _git gstl=git-stash
gstp()  { git stash pop "$@"; }; compdef _git gstp=git-stash
gsts()  { git stash show -p "$@"; }; compdef _git gsts=git-stash
gstu()  { git stash -u "$@"; }; compdef _git gstu=git-stash
gsw()   { git switch "$@"; }; compdef _git gsw=git-switch
gswc()  { git switch -c "$@"; }; compdef _git gswc=git-switch
gswcd() { git switch -c "$1" $(get_default_branch); }; compdef _git gswcd=git-switch
gswd()  { git switch $(get_default_branch); }; compdef _git gswd=git-switch
gswp()  { git switch - "$@"; }; compdef _git gswd=git-switch
gtc()   { gt create "$@"; }
gtl()   { gt log "$@"; }
gtm()   { gt move "$@"; }
gtr()   { gt restack "$@"; }
gts()   { gt submit "$@"; }
gtsy()  { gt sync "$@"; }
gtt()   { gt track "$@"; }
alias gwip='git add . && git commit -m "WIP" -n'

Sort committers by average length of commit message

git log --no-merges --pretty=format:'COMMIT_START|%an|%B|COMMIT_END' | \
awk '
BEGIN { RS = "COMMIT_END\n"; FS = "|" }
/COMMIT_START/ {
    author = $2
    message = ""
    for (i = 3; i <= NF; i++) {
        if (i > 3) message = message "|"
        message = message $i
    }
    len = length(message)
    authors[author] += len
    counts[author]++
    if (!min[author] || len < min[author]) min[author] = len
    if (len > max[author]) max[author] = len
}
END {
    for (author in authors) {
        avg = authors[author] / counts[author]
        printf "%6.1f|%-30s|(min: %d, max: %d, commits: %d)\n",
               avg, author, min[author], max[author], counts[author]
    }
}' | sort -t'|' -k1 -nr | awk -F'|' '{printf "%-30s %s chars %s\n", $2, $1, $3}'

Gradient Borders with Transparent Background

<html lang="en">
<head>
<title>Gradient borders with transparent background</title>
<meta charset="utf-8">
<style>
* {
  box-sizing: border-box;
}

body {
  background-color: black;
  color: white;
}

.card {
  background-color: gray;
  color: black;
}

.gradient-border-mask {
  display: flow-root;
  position: relative;
  padding: 1.3rem;
}

.gradient-border-mask::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  border-radius: 5px;
  border: 5px solid transparent;
  background: linear-gradient(45deg, purple, orange) border-box;
  -webkit-mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: destination-out;
  mask-composite: exclude;
}
</style>

</head>
<body>

<div class="gradient-border-mask">
  <p>Lorem ipsum dolor sit amet consectetur</p>
</div>

<div class="card">
  <p>Lorem ipsum dolor sit amet consectetur</p>
  <div class="gradient-border-mask">
    <p>Lorem ipsum dolor sit amet consectetur</p>
  </div>
</div>

</body>
</html>

How to keep your input and dismiss/cancel atuin

The normal behavior of atuin is to put either the command you matched onto your commandline, or nothing. So if you Ctrl-r and type something with no good match and exit, you “lose” what you have typed. You can Ctrl-g to get back what you had when you pressed Ctrl-r but anything you typed after that is lost.

To get what I think is superior behavior set exit_mode = "return_query" in ~/.config/atuin/config.toml. Now whatever is in the atuin buffer is now on your commandline if you hit Esc. You can still Ctrl-g to get what you had before Ctrl-r or Ctrl-c to exit to an empty commandline.

Starting more than one dev webserver each in its own terminal window (Mac)

#!/usr/bin/env zsh
# Script to start monorepo development processes in separate terminals
open_position_run() {
  local dir=$1
  local cmd=$2

  # Start the server
  osascript -e 'tell application "Terminal"
    do script "cd '"$dir"' && nvm use 20 && '"$cmd"' &" in window 1
  end tell'
}

open_position_run "/Users/bakert/monorepo/apps/app1 "pnpm dev"
open_position_run "/Users/bakert/monorepo/apps/app2 "pnpm dev"

History (Fourth Time)

I did this in 2008, 2011 and 2015 but forgot to do it for ten years.

[zarniwoop ~] history 0 | awk '{a[$2]++}END{for(i in a){print a[i] " " i}}' | sort -rn  | head
34675 git
5294 composer
4376 cd
3761 vi
3076 pnpm
2914 python
2188 m
1917 ls
1822 python3
1717 ssh

Compared to 2015. New: composer, pnpm, python/python3, m (run scripts for an app I work on). Climbers: git, cd. Fallers: ssh, ls. Old: c, php, pass, sudo, ruby.

COUNT(*) OVER () in Drizzle

In general I like to use COUNT(*) OVER () to get the total count for a query when retrieving a slice with OFFSET and LIMIT (very useful for pagination) rather than issuing a separate query for the total count. This is not super well supported in Drizzle but you can do it like this:

const data = await db.query.tableName.findMany({
    …
    extras: {
        // Weird cast here because COUNT(*) is a bigint which is handled as a string in js
        // https://orm.drizzle.team/docs/guides/count-rows
        total: sql<number>`CAST(COUNT(*) OVER() AS INTEGER)`.as("total"),
    },
    …