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.
