diff --git a/scripts/post-create.sh b/scripts/post-create.sh index 431e944d74..7ca17551aa 100755 --- a/scripts/post-create.sh +++ b/scripts/post-create.sh @@ -11,15 +11,5 @@ if [[ -z "$backend_port" || -z "$frontend_port" ]]; then exit 1 fi -cat > .env.local <> .env.local -fi - -echo "Created .env.local with ports: backend=$backend_port, frontend=$frontend_port" +wm_write_env_local "$(pwd)" "$backend_port" "$frontend_port" wm_shared_post_create "$(pwd)" diff --git a/scripts/worktree-common.sh b/scripts/worktree-common.sh index 672b9f9f65..fd741f6312 100755 --- a/scripts/worktree-common.sh +++ b/scripts/worktree-common.sh @@ -13,6 +13,65 @@ wm_main_repo_root() { cd "$(git -C "$repo_root" rev-parse --git-common-dir 2>/dev/null)/.." && pwd } +wm_port_in_use() { + lsof -nP -iTCP:"$1" -sTCP:LISTEN &>/dev/null +} + +# Prints " " for the lowest free slot. Slots are read from +# each worktree's .env.local rather than from position in `git worktree list`: removing a +# worktree in the middle would otherwise hand its slot to a new one while a live +# neighbour still listens on those ports. Slot 0 belongs to the main checkout. +wm_assign_ports() { + local repo_root=$1 + local slot=${WM_SLOT:-} + local used_slots=() wt_path backend_port frontend_port bp + + if [[ -z "$slot" ]]; then + while IFS= read -r wt_path; do + [[ "$wt_path" == "$repo_root" ]] && continue + [[ -f "$wt_path/.env.local" ]] || continue + bp=$(grep '^BACKEND_PORT=' "$wt_path/.env.local" | cut -d= -f2 || true) + if [[ -n "$bp" && "$bp" -gt 8000 ]]; then + used_slots+=("$(( (bp - 8000) / 10 ))") + fi + done < <(git -C "$repo_root" worktree list --porcelain | sed -n 's/^worktree //p') + + slot=1 + while [[ " ${used_slots[*]:-} " == *" $slot "* ]]; do + ((slot++)) + done + echo "Auto-assigned slot $slot (used: ${used_slots[*]:-none})" >&2 + fi + + backend_port=$((8000 + slot * 10)) + frontend_port=$((3000 + slot * 10)) + + if wm_port_in_use "$backend_port" || wm_port_in_use "$frontend_port"; then + echo "WARNING: slot $slot ports ($backend_port/$frontend_port) already in use" >&2 + fi + + printf '%s %s\n' "$backend_port" "$frontend_port" +} + +wm_write_env_local() { + local repo_root=$1 + local backend_port=$2 + local frontend_port=$3 + local env_file="${repo_root}/.env.local" + + cat > "$env_file" <> "$env_file" + fi + + echo "Created .env.local with ports: backend=$backend_port, frontend=$frontend_port" +} + wm_setup_database() { local repo_root=$1 local env_file=$2 diff --git a/scripts/worktree-env b/scripts/worktree-env index e45daba75b..390a12fd66 100755 --- a/scripts/worktree-env +++ b/scripts/worktree-env @@ -3,54 +3,6 @@ set -euo pipefail source "$(dirname "${BASH_SOURCE[0]}")/worktree-common.sh" -port_in_use() { - lsof -nP -iTCP:"$1" -sTCP:LISTEN &>/dev/null -} - -if [[ -z "${WM_SLOT:-}" ]]; then - # Scan .env.local files of existing worktrees to find which slots are claimed, - # then pick the lowest free slot. This avoids collisions when worktrees are - # removed and new ones created (position-based indexing would re-use slots - # still held by surviving worktrees). - used_slots=() - current_dir="$(pwd)" - while IFS= read -r wt_path; do - [[ "$wt_path" == "$current_dir" ]] && continue - if [[ -f "$wt_path/.env.local" ]]; then - bp=$(grep '^BACKEND_PORT=' "$wt_path/.env.local" | cut -d= -f2 || true) - if [[ -n "$bp" && "$bp" -gt 8000 ]]; then - used_slots+=("$(( (bp - 8000) / 10 ))") - fi - fi - done < <(git worktree list --porcelain | sed -n 's/^worktree //p') - - # Find lowest available slot (slot 0 = 8000/3000 is reserved for main) - WM_SLOT=1 - while [[ " ${used_slots[*]:-} " == *" $WM_SLOT "* ]]; do - ((WM_SLOT++)) - done - echo "Auto-assigned slot $WM_SLOT (used: ${used_slots[*]:-none})" -fi - -# Slot-based: predictable ports for SSH forwarding -# Slot 0 = 8000/3000, slot 1 = 8010/3010, slot 2 = 8020/3020, etc. -backend_port=$((8000 + WM_SLOT * 10)) -frontend_port=$((3000 + WM_SLOT * 10)) - -if port_in_use "$backend_port" || port_in_use "$frontend_port"; then - echo "WARNING: Slot $WM_SLOT ports ($backend_port/$frontend_port) already in use" >&2 -fi - -# Generate .env.local with port overrides -cat > .env.local <> .env.local -fi - -echo "Created .env.local with ports: backend=$backend_port, frontend=$frontend_port" +read -r backend_port frontend_port < <(wm_assign_ports "$(pwd)") +wm_write_env_local "$(pwd)" "$backend_port" "$frontend_port" wm_shared_post_create "$(pwd)"