mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-25 00:01:55 +00:00
7a8cd3b5c6
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>
334 lines
11 KiB
Bash
Executable File
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
|
|
|
|
}
|