* feat: optionally enable rust-analyzer plugin in worktree settings When USE_RUST_PLUGIN env var is set, the worktree-env script now includes the rust-analyzer-lsp plugin in .claude/settings.local.json. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: remove rust-analyzer plugin from default settings The rust-analyzer plugin is now opt-in via USE_RUST_PLUGIN env var in worktree-env, so it no longer needs to be in the shared settings. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: add WM_CLONE_DB and USE_RUST_PLUGIN to wmdev startup envs Defaults both to false so they can be toggled per-worktree. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: use explicit truthy checks for WM_CLONE_DB and USE_RUST_PLUGIN Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
204 lines
7.9 KiB
Bash
Executable File
204 lines
7.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
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 <<EOF
|
|
BACKEND_PORT=$backend_port
|
|
FRONTEND_PORT=$frontend_port
|
|
REMOTE=http://localhost:$backend_port
|
|
EOF
|
|
|
|
if [[ -n "${CARGO_FEATURES:-}" ]]; then
|
|
echo "CARGO_FEATURES=$CARGO_FEATURES" >> .env.local
|
|
fi
|
|
|
|
echo "Created .env.local with ports: backend=$backend_port, frontend=$frontend_port"
|
|
|
|
# --- Create per-worktree database ---
|
|
wt_basename=$(basename "$(pwd)")
|
|
db_name="windmill_${wt_basename//-/_}"
|
|
db_conn="postgres://postgres:changeme@127.0.0.1:5432"
|
|
|
|
if command -v psql &>/dev/null; then
|
|
if psql "$db_conn/postgres" -tc "SELECT 1 FROM pg_database WHERE datname = '${db_name}'" 2>/dev/null | grep -q 1; then
|
|
echo "Database $db_name already exists"
|
|
else
|
|
if [[ "${WM_CLONE_DB:-}" == "1" || "${WM_CLONE_DB:-}" == "true" ]]; then
|
|
# Terminate active connections so CREATE DATABASE ... TEMPLATE works
|
|
psql "$db_conn/postgres" -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'windmill' AND pid <> pg_backend_pid();" 2>/dev/null || true
|
|
psql "$db_conn/postgres" -c "CREATE DATABASE ${db_name} TEMPLATE windmill" 2>/dev/null \
|
|
&& echo "Created database $db_name (template: windmill)" \
|
|
|| echo "WARNING: Could not create database $db_name from template windmill" >&2
|
|
else
|
|
psql "$db_conn/postgres" -c "CREATE DATABASE ${db_name}" 2>/dev/null \
|
|
&& echo "Created database $db_name" \
|
|
|| echo "WARNING: Could not create database $db_name (is PostgreSQL running?)" >&2
|
|
fi
|
|
fi
|
|
db_url="${db_conn}/${db_name}?sslmode=disable"
|
|
# Run migrations against the new database
|
|
DATABASE_URL="$db_url" sqlx migrate run --source backend/migrations \
|
|
&& echo "Migrations applied to $db_name" \
|
|
|| echo "WARNING: Could not run migrations on $db_name" >&2
|
|
# Copy license_key from the main windmill database to the new database
|
|
license_key=$(psql "$db_conn/windmill" -t -A -c "SELECT value FROM global_settings WHERE name = 'license_key'" 2>/dev/null || true)
|
|
if [[ -n "$license_key" ]]; then
|
|
psql "$db_url" -c "INSERT INTO global_settings (name, value) VALUES ('license_key', '${license_key}'::jsonb) ON CONFLICT (name) DO UPDATE SET value = EXCLUDED.value" 2>/dev/null \
|
|
&& echo "Copied license_key to $db_name" \
|
|
|| echo "WARNING: Could not copy license_key to $db_name" >&2
|
|
fi
|
|
# Use export so DATABASE_URL overrides the nix devshell value for child processes
|
|
cat >> .env.local <<EOF
|
|
export DATABASE_URL=$db_url
|
|
WM_DB_NAME=$db_name
|
|
EOF
|
|
echo "Added DATABASE_URL for database $db_name to .env.local"
|
|
else
|
|
echo "WARNING: psql not found, skipping per-worktree database creation" >&2
|
|
fi
|
|
|
|
# --- Copy frontend/node_modules preserving symlinks ---
|
|
# cp -a preserves .bin/ symlinks that cp -r would dereference, breaking require() paths
|
|
main_repo_root="$(cd "$(git rev-parse --git-common-dir 2>/dev/null)/.." && pwd)"
|
|
if [[ -n "$main_repo_root" && -d "$main_repo_root/frontend/node_modules" ]]; then
|
|
cp -a "$main_repo_root/frontend/node_modules" frontend/
|
|
echo "Copied frontend/node_modules (with symlinks preserved)"
|
|
fi
|
|
|
|
# --- Install cli deps and generate client ---
|
|
if [[ -n "$main_repo_root" && -d "$main_repo_root/cli/node_modules" ]]; then
|
|
cp -a "$main_repo_root/cli/node_modules" cli/
|
|
echo "Copied cli/node_modules (with symlinks preserved)"
|
|
fi
|
|
(cd cli && npm install && npm run gen-client) \
|
|
&& echo "CLI deps installed and client generated" \
|
|
|| echo "WARNING: CLI setup failed" >&2
|
|
|
|
# --- Allow direnv so the nix devshell activates in pane commands ---
|
|
if command -v direnv &>/dev/null && [ -f .envrc ]; then
|
|
direnv allow
|
|
echo "direnv allowed"
|
|
fi
|
|
|
|
# --- Trust worktree directory in Claude Code ---
|
|
claude_json="$HOME/.claude.json"
|
|
if [ -f "$claude_json" ]; then
|
|
wt_path="$(pwd)"
|
|
python3 -c "
|
|
import json, sys
|
|
path = '$wt_path'
|
|
with open('$claude_json', 'r') as f:
|
|
data = json.load(f)
|
|
projects = data.setdefault('projects', {})
|
|
proj = projects.setdefault(path, {})
|
|
proj['hasTrustDialogAccepted'] = True
|
|
proj['hasCompletedProjectOnboarding'] = True
|
|
with open('$claude_json', 'w') as f:
|
|
json.dump(data, f, indent=2)
|
|
" && echo "Added $wt_path to Claude Code trusted directories" \
|
|
|| echo "Warning: Could not update Claude Code trusted directories"
|
|
fi
|
|
|
|
# --- Create matching windmill-ee-private worktree ---
|
|
# Find ee repo: sibling to the main worktree (git toplevel of the main checkout),
|
|
# then try parent of cwd, then fall back to home
|
|
ee_repo=""
|
|
for candidate in \
|
|
"${main_repo_root:+${main_repo_root}/../windmill-ee-private}" \
|
|
"$(pwd)/../windmill-ee-private" \
|
|
"${HOME}/windmill-ee-private" \
|
|
"${HOME}/projects/windmill-ee-private"; do
|
|
if [ -n "$candidate" ] && [ -d "$candidate" ]; then
|
|
ee_repo="$(cd "$candidate" && pwd)"
|
|
break
|
|
fi
|
|
done
|
|
if [ -n "$ee_repo" ]; then
|
|
branch=$(git branch --show-current 2>/dev/null || true)
|
|
wt_basename=$(basename "$(pwd)")
|
|
ee_worktree_dir="${ee_repo}__worktrees/${wt_basename}"
|
|
|
|
if [ -n "$branch" ] && [ ! -d "$ee_worktree_dir" ]; then
|
|
mkdir -p "$(dirname "$ee_worktree_dir")"
|
|
|
|
# Fetch latest so we can check out remote branches
|
|
git -C "$ee_repo" fetch --quiet 2>/dev/null || true
|
|
|
|
# Try: existing branch, then new branch from main
|
|
if git -C "$ee_repo" worktree add "$ee_worktree_dir" "$branch" 2>/dev/null; then
|
|
echo "Created EE worktree at $ee_worktree_dir (branch: $branch)"
|
|
elif git -C "$ee_repo" worktree add -b "$branch" "$ee_worktree_dir" main 2>/dev/null; then
|
|
echo "Created EE worktree at $ee_worktree_dir (new branch: $branch from main)"
|
|
else
|
|
echo "Warning: Could not create EE worktree for branch $branch"
|
|
fi
|
|
elif [ -d "$ee_worktree_dir" ]; then
|
|
echo "EE worktree already exists at $ee_worktree_dir"
|
|
fi
|
|
|
|
# Point Claude Code additionalDirectories at the EE worktree
|
|
if [ -d "$ee_worktree_dir" ]; then
|
|
ee_rel=$(python3 -c "import os; print(os.path.relpath('$ee_worktree_dir', '$(pwd)'))" 2>/dev/null || echo "$ee_worktree_dir")
|
|
mkdir -p .claude
|
|
rust_plugin=""
|
|
if [[ "${USE_RUST_PLUGIN:-}" == "1" || "${USE_RUST_PLUGIN:-}" == "true" ]]; then
|
|
rust_plugin=',
|
|
"enabledPlugins": {
|
|
"rust-analyzer-lsp@claude-plugins-official": true
|
|
}'
|
|
fi
|
|
cat > .claude/settings.local.json <<EOFCLAUDE
|
|
{
|
|
"permissions": {
|
|
"additionalDirectories": [
|
|
"$ee_rel"
|
|
]
|
|
}${rust_plugin}
|
|
}
|
|
EOFCLAUDE
|
|
echo "Created .claude/settings.local.json with EE path: $ee_rel"
|
|
|
|
# Create symlinks from backend crates to the EE worktree
|
|
if [ -x "./backend/substitute_ee_code.sh" ]; then
|
|
./backend/substitute_ee_code.sh -d "$ee_worktree_dir"
|
|
fi
|
|
fi
|
|
fi |