Files
windmill/scripts/worktree-common.sh
T
hugocasa 7a8cd3b5c6 chore: narrow the shared helper to .env.local writing
Slot assignment goes back into worktree-env: it had one caller, and the herdr plugin it was extracted for provisions worktrees before the checkout is usable, so it carries its own copy rather than sourcing one whose contents depend on whichever branch the main checkout sits on.

Writing .env.local stays shared - worktree-env and post-create.sh each had the same eleven lines.

Also corrects AGENTS.md: .env.local is a shared format rather than a shared function, and WM_DB_NAME comes from it rather than webmux's runtime.env.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 16:33:31 +02:00

334 lines
11 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
wm_is_true() {
case "${1:-}" in
1|true|TRUE|yes|YES|on|ON) return 0 ;;
*) return 1 ;;
esac
}
wm_main_repo_root() {
local repo_root=${1:-.}
cd "$(git -C "$repo_root" rev-parse --git-common-dir 2>/dev/null)/.." && pwd
}
# Written by both worktree entry points — `worktree-env` by hand, `post-create.sh` from the
# webmux hook — and read by agents to find their own ports, so the two must not drift.
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" <<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_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
local wt_basename db_name db_conn db_url license_key
wt_basename="$(basename "$repo_root")"
db_name="windmill_${wt_basename//-/_}"
db_conn="postgres://postgres:changeme@127.0.0.1:5432"
if ! command -v psql >/dev/null 2>&1; then
echo "WARNING: psql not found, skipping per-worktree database creation" >&2
return
fi
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_is_true "${WM_CLONE_DB:-}"; then
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"
DATABASE_URL="$db_url" sqlx migrate run --source "${repo_root}/backend/migrations" \
&& echo "Migrations applied to $db_name" \
|| echo "WARNING: Could not run migrations on $db_name" >&2
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
cat >> "$env_file" <<EOF
export DATABASE_URL=$db_url
WM_DB_NAME=$db_name
EOF
echo "Added DATABASE_URL for database $db_name to .env.local"
}
wm_copy_dependencies() {
local repo_root=$1
local main_repo_root=$2
if [[ -f "${main_repo_root}/backend/.env" ]]; then
cp "${main_repo_root}/backend/.env" "${repo_root}/backend/"
echo "Copied backend/.env"
fi
if [[ -d "${main_repo_root}/frontend/node_modules" ]]; then
cp -a "${main_repo_root}/frontend/node_modules" "${repo_root}/frontend/"
echo "Copied frontend/node_modules (with symlinks preserved)"
fi
if [[ -d "${main_repo_root}/cli/node_modules" ]]; then
cp -a "${main_repo_root}/cli/node_modules" "${repo_root}/cli/"
echo "Copied cli/node_modules (with symlinks preserved)"
fi
if [[ -d "${repo_root}/cli" ]]; then
(cd "${repo_root}/cli" && npm install && npm run gen-client) \
&& echo "CLI deps installed and client generated" \
|| echo "WARNING: CLI setup failed" >&2
fi
local nav_bin="${main_repo_root}/wm-ts-nav/target/release/wm-ts-nav"
if [[ -f "$nav_bin" ]]; then
mkdir -p "${repo_root}/wm-ts-nav/target/release"
cp "$nav_bin" "${repo_root}/wm-ts-nav/target/release/"
echo "Copied wm-ts-nav binary"
fi
}
wm_copy_optional_env_files() {
local repo_root=$1
local main_repo_root=$2
local rel_path src_env dst_dir
for rel_path in "integration_tests/ai_agent_tests" "ai_evals"; do
src_env="${main_repo_root}/${rel_path}/.env"
dst_dir="${repo_root}/${rel_path}"
if [[ -f "$src_env" ]]; then
mkdir -p "$dst_dir"
cp "$src_env" "${dst_dir}/.env"
echo "Copied ${rel_path}/.env"
fi
done
}
wm_allow_direnv() {
local repo_root=$1
if command -v direnv >/dev/null 2>&1 && [[ -f "${repo_root}/.envrc" ]]; then
(cd "$repo_root" && direnv allow)
echo "direnv allowed"
fi
}
wm_trust_claude() {
local repo_root=$1
local claude_json="${HOME}/.claude.json"
if [[ ! -f "$claude_json" ]] || ! command -v python3 >/dev/null 2>&1; then
return
fi
REPO_ROOT="$repo_root" CLAUDE_JSON="$claude_json" python3 - <<'PY' \
&& echo "Added $repo_root to Claude Code trusted directories" \
|| echo "Warning: Could not update Claude Code trusted directories"
import json
import os
path = os.environ["REPO_ROOT"]
claude_json = os.environ["CLAUDE_JSON"]
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)
PY
}
wm_find_ee_repo() {
local repo_root=$1
local main_repo_root=$2
local candidate
for candidate in \
"${main_repo_root}/../windmill-ee-private" \
"${repo_root}/../windmill-ee-private" \
"${HOME}/windmill-ee-private" \
"${HOME}/projects/windmill-ee-private"; do
if [[ -d "$candidate" ]]; then
cd "$candidate" && pwd
return 0
fi
done
return 1
}
# The EE commit this CE checkout builds against. CI reads the same file, so basing a new EE
# worktree on it keeps a local `cargo check --features private` on the tree CI compiles. Local
# `main` in the EE repo is not a substitute: nothing fast-forwards it, so it drifts behind the pin.
wm_ee_pinned_ref() {
local repo_root=$1 ref
ref="$(tr -d '[:space:]' < "${repo_root}/backend/ee-repo-ref.txt" 2>/dev/null)" || return 1
[[ -n "$ref" ]] || return 1
printf '%s' "$ref"
}
wm_setup_ee_worktree() {
local repo_root=$1
local main_repo_root=$2
local ee_repo branch wt_basename ee_worktree_dir ee_rel rust_plugin ee_ref
if ! ee_repo="$(wm_find_ee_repo "$repo_root" "$main_repo_root")"; then
return
fi
branch="$(git -C "$repo_root" branch --show-current 2>/dev/null || true)"
wt_basename="$(basename "$repo_root")"
ee_worktree_dir="${ee_repo}__worktrees/${wt_basename}"
if [[ -n "$branch" && ! -d "$ee_worktree_dir" ]]; then
mkdir -p "$(dirname "$ee_worktree_dir")"
git -C "$ee_repo" fetch --quiet 2>/dev/null || true
# Guard on refs/heads: without it, `worktree add` DWIMs a remote-only branch
# into a new local branch here, skipping the explicit --track arm below.
if git -C "$ee_repo" show-ref --verify --quiet "refs/heads/$branch" \
&& git -C "$ee_repo" worktree add "$ee_worktree_dir" "$branch" 2>/dev/null; then
echo "Created EE worktree at $ee_worktree_dir (existing local branch: $branch)"
elif git -C "$ee_repo" show-ref --verify --quiet "refs/remotes/origin/$branch" \
&& git -C "$ee_repo" worktree add --track -b "$branch" "$ee_worktree_dir" "origin/$branch" 2>/dev/null; then
echo "Created EE worktree at $ee_worktree_dir (tracking origin/$branch)"
elif ee_ref="$(wm_ee_pinned_ref "$repo_root")" \
&& git -C "$ee_repo" worktree add -b "$branch" "$ee_worktree_dir" "$ee_ref" 2>/dev/null; then
echo "Created EE worktree at $ee_worktree_dir (new branch: $branch from pinned ${ee_ref:0:12})"
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 — pin unavailable)"
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
if [[ ! -d "$ee_worktree_dir" ]]; then
return
fi
ee_rel="$(REPO_ROOT="$repo_root" EE_WORKTREE_DIR="$ee_worktree_dir" python3 - <<'PY' 2>/dev/null || echo "$ee_worktree_dir"
import os
print(os.path.relpath(os.environ["EE_WORKTREE_DIR"], os.environ["REPO_ROOT"]))
PY
)"
mkdir -p "${repo_root}/.claude"
rust_plugin=""
if wm_is_true "${USE_RUST_PLUGIN:-}"; then
rust_plugin=',
"enabledPlugins": {
"rust-analyzer-lsp@claude-plugins-official": true
}'
fi
cat > "${repo_root}/.claude/settings.local.json" <<EOF
{
"permissions": {
"additionalDirectories": [
"$ee_rel"
]
}${rust_plugin}
}
EOF
echo "Created .claude/settings.local.json with EE path: $ee_rel"
if [[ -x "${repo_root}/backend/substitute_ee_code.sh" ]]; then
"${repo_root}/backend/substitute_ee_code.sh" -d "$ee_worktree_dir"
fi
}
wm_shared_post_create() {
local repo_root=$1
local main_repo_root
main_repo_root="$(wm_main_repo_root "$repo_root")"
wm_allow_direnv "$repo_root"
wm_setup_database "$repo_root" "${repo_root}/.env.local"
wm_copy_dependencies "$repo_root" "$main_repo_root"
wm_copy_optional_env_files "$repo_root" "$main_repo_root"
wm_trust_claude "$repo_root"
wm_setup_ee_worktree "$repo_root" "$main_repo_root"
}
wm_kill_processes_from_env_file() {
local env_file=$1
local pid port
if [[ ! -f "$env_file" ]]; then
return
fi
# shellcheck disable=SC1090
source "$env_file"
for port in "${BACKEND_PORT:-}" "${FRONTEND_PORT:-}"; do
[[ -z "$port" ]] && continue
pid="$(lsof -ti "TCP:${port}" -sTCP:LISTEN 2>/dev/null || true)"
if [[ -n "$pid" ]]; then
kill "$pid" 2>/dev/null && echo "Killed process $pid on port $port" \
|| echo "Warning: Could not kill process $pid on port $port"
fi
done
}
wm_shared_pre_remove() {
local repo_root=$1
local env_file="${repo_root}/.env.local"
local db_conn wt_basename main_repo_root ee_repo ee_worktree_dir
if [[ -f "$env_file" ]]; then
# shellcheck disable=SC1090
source "$env_file"
fi
if [[ -n "${WM_DB_NAME:-}" ]]; then
db_conn="postgres://postgres:changeme@127.0.0.1:5432"
if command -v psql >/dev/null 2>&1; then
psql "$db_conn/postgres" -c "DROP DATABASE IF EXISTS ${WM_DB_NAME} WITH (FORCE)" 2>/dev/null \
&& echo "Dropped database $WM_DB_NAME" \
|| echo "Warning: Could not drop database $WM_DB_NAME"
else
echo "psql not found, skipping database cleanup for $WM_DB_NAME"
fi
fi
main_repo_root="$(wm_main_repo_root "$repo_root")"
wt_basename="$(basename "$repo_root")"
if ee_repo="$(wm_find_ee_repo "$repo_root" "$main_repo_root")"; then
ee_worktree_dir="${ee_repo}__worktrees/${wt_basename}"
if [[ -d "$ee_worktree_dir" ]]; then
git -C "$ee_repo" worktree remove "$ee_worktree_dir" --force 2>/dev/null \
&& echo "Removed EE worktree at $ee_worktree_dir" \
|| echo "Warning: Could not remove EE worktree at $ee_worktree_dir"
fi
fi
}