wt-new.sh 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. #!/usr/bin/env bash
  2. #############################################################################
  3. # wt-new.sh — Create a new git worktree as a sibling directory
  4. #
  5. # Usage: wt-new.sh <branch> [name]
  6. # branch Git branch name, e.g. feature/auth or bugfix/payments
  7. # name Short directory suffix, defaults to branch with / replaced by -
  8. # e.g. feature/auth → feature-auth
  9. #
  10. # Output structure (siblings to the main repo):
  11. # ~/…/github/
  12. # my-app/ ← main worktree (this repo)
  13. # my-app-feature-auth/ ← new worktree
  14. #
  15. # Port scheme: BASE + (INDEX * STEP) where INDEX = number of existing worktrees
  16. # Index 0 (main): web=3000 admin=3001 api=3002 db=5432 redis=6379
  17. # Index 1: web=3010 admin=3011 api=3012 db=5442 redis=6389
  18. # Index 2: web=3020 admin=3021 api=3022 db=5452 redis=6399
  19. #############################################################################
  20. set -euo pipefail
  21. # ── Locate main repo root ─────────────────────────────────────────────────
  22. find_git_root() {
  23. local dir
  24. dir="$(pwd)"
  25. while [ "$dir" != "/" ]; do
  26. if [ -d "$dir/.git" ]; then
  27. echo "$dir"
  28. return 0
  29. fi
  30. dir="$(dirname "$dir")"
  31. done
  32. echo "ERROR: not inside a git repository" >&2
  33. return 1
  34. }
  35. MAIN_REPO="$(find_git_root)"
  36. GITHUB_ROOT="$(dirname "$MAIN_REPO")"
  37. REPO_NAME="$(basename "$MAIN_REPO")"
  38. # ── Args ──────────────────────────────────────────────────────────────────
  39. BRANCH="${1:-}"
  40. NAME="${2:-}"
  41. if [[ -z "$BRANCH" ]]; then
  42. echo "Usage: wt-new.sh <branch> [name]"
  43. echo " e.g. wt-new.sh feature/auth feature-auth"
  44. exit 1
  45. fi
  46. # Default name: replace / with -
  47. if [[ -z "$NAME" ]]; then
  48. NAME="${BRANCH//\//-}"
  49. fi
  50. WORKTREE_DIR="$GITHUB_ROOT/${REPO_NAME}-${NAME}"
  51. # ── Compute index ─────────────────────────────────────────────────────────
  52. # Count existing worktrees (main counts as index 0, so subtract 1)
  53. INDEX=$(git -C "$MAIN_REPO" worktree list | wc -l | tr -d ' ')
  54. INDEX=$((INDEX - 1))
  55. BASE_WEB=3000
  56. BASE_ADMIN=3001
  57. BASE_API=3002
  58. BASE_DB=5432
  59. BASE_REDIS=6379
  60. STEP=10
  61. WEB_PORT=$((BASE_WEB + INDEX * STEP))
  62. ADMIN_PORT=$((BASE_ADMIN + INDEX * STEP))
  63. API_PORT=$((BASE_API + INDEX * STEP))
  64. DB_PORT=$((BASE_DB + INDEX * STEP))
  65. REDIS_PORT=$((BASE_REDIS + INDEX * STEP))
  66. echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  67. echo " Creating worktree: ${REPO_NAME}-${NAME}"
  68. echo " Branch: $BRANCH"
  69. echo " Path: $WORKTREE_DIR"
  70. echo " Index: $INDEX"
  71. echo " Ports: web=$WEB_PORT admin=$ADMIN_PORT api=$API_PORT db=$DB_PORT redis=$REDIS_PORT"
  72. echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  73. if [[ -d "$WORKTREE_DIR" ]]; then
  74. echo "ERROR: directory already exists: $WORKTREE_DIR" >&2
  75. exit 1
  76. fi
  77. # ── Create worktree ───────────────────────────────────────────────────────
  78. cd "$MAIN_REPO"
  79. if git show-ref --verify --quiet "refs/heads/$BRANCH"; then
  80. echo "→ Branch exists locally — adding worktree"
  81. git worktree add "$WORKTREE_DIR" "$BRANCH"
  82. elif git show-ref --verify --quiet "refs/remotes/origin/$BRANCH"; then
  83. echo "→ Branch exists on remote — checking out"
  84. git worktree add -b "$BRANCH" "$WORKTREE_DIR" "origin/$BRANCH"
  85. else
  86. echo "→ New branch — creating from origin/main"
  87. git worktree add -b "$BRANCH" "$WORKTREE_DIR" origin/main
  88. fi
  89. cd "$WORKTREE_DIR"
  90. # ── Install dependencies ──────────────────────────────────────────────────
  91. if [[ -f "pnpm-lock.yaml" ]]; then
  92. echo "→ Installing dependencies (pnpm)..."
  93. pnpm install
  94. elif [[ -f "yarn.lock" ]]; then
  95. echo "→ Installing dependencies (yarn)..."
  96. yarn install
  97. elif [[ -f "package-lock.json" ]]; then
  98. echo "→ Installing dependencies (npm)..."
  99. npm install
  100. else
  101. echo " (no lock file found — skipping install)"
  102. fi
  103. # ── Generate .env files ───────────────────────────────────────────────────
  104. TEMPLATE="$MAIN_REPO/.env.template"
  105. if [[ -f "$TEMPLATE" ]]; then
  106. echo "→ Generating .env files from .env.template..."
  107. generate_env() {
  108. local dest="$1"
  109. local dir
  110. dir="$(dirname "$dest")"
  111. mkdir -p "$dir"
  112. sed \
  113. -e "s/__INDEX__/$INDEX/g" \
  114. -e "s/__NAME__/$NAME/g" \
  115. -e "s/__WEB_PORT__/$WEB_PORT/g" \
  116. -e "s/__ADMIN_PORT__/$ADMIN_PORT/g" \
  117. -e "s/__API_PORT__/$API_PORT/g" \
  118. -e "s/__DB_PORT__/$DB_PORT/g" \
  119. -e "s/__REDIS_PORT__/$REDIS_PORT/g" \
  120. "$TEMPLATE" > "$dest"
  121. echo " ✓ $dest"
  122. }
  123. # Root .env
  124. generate_env "$WORKTREE_DIR/.env"
  125. # Per-app .env files (only if the app directories exist)
  126. for app_dir in apps/web apps/admin apps/api; do
  127. if [[ -d "$WORKTREE_DIR/$app_dir" ]]; then
  128. generate_env "$WORKTREE_DIR/$app_dir/.env"
  129. fi
  130. done
  131. else
  132. echo " (no .env.template found — skipping .env generation)"
  133. echo " Tip: add a .env.template to your repo root for automatic port-isolated .env files"
  134. fi
  135. # ── Copy .env.local secrets from main worktree ───────────────────────────
  136. for extra in ".env.local" "apps/web/.env.local" "apps/admin/.env.local" "apps/api/.env.local"; do
  137. if [[ -f "$MAIN_REPO/$extra" ]]; then
  138. dest_dir="$(dirname "$WORKTREE_DIR/$extra")"
  139. mkdir -p "$dest_dir"
  140. cp "$MAIN_REPO/$extra" "$WORKTREE_DIR/$extra"
  141. echo " ✓ copied $extra"
  142. fi
  143. done
  144. # ── Launch in Zellij (optional) ───────────────────────────────────────────
  145. if command -v zellij &>/dev/null; then
  146. echo "→ Launching Zellij session: $NAME"
  147. zellij attach -c "$NAME" --layout worktree 2>/dev/null || zellij attach -c "$NAME"
  148. else
  149. echo ""
  150. echo "✓ Worktree ready!"
  151. echo " cd $WORKTREE_DIR"
  152. fi