chore: webmux config#8279
This commit is contained in:
139
.webmux.yaml
139
.webmux.yaml
@@ -1,113 +1,52 @@
|
||||
name: Windmill
|
||||
# Project display name in the dashboard
|
||||
name: windmill
|
||||
|
||||
workspace:
|
||||
mainBranch: main
|
||||
worktreeRoot: ../__worktrees
|
||||
defaultAgent: claude
|
||||
|
||||
startupEnvs:
|
||||
CARGO_FEATURES: "quickjs"
|
||||
WM_CLONE_DB: false
|
||||
USE_RUST_PLUGIN: false
|
||||
|
||||
lifecycleHooks:
|
||||
postCreate: bash ./scripts/post-create.sh
|
||||
preRemove: bash ./scripts/pre-remove.sh
|
||||
|
||||
# Each service defines a port env var that webmux injects into pane and agent
|
||||
# process environments when creating a worktree. Ports are auto-assigned:
|
||||
# base + (slot x step).
|
||||
services:
|
||||
- name: BE
|
||||
- name: backend
|
||||
portEnv: BACKEND_PORT
|
||||
- name: FE
|
||||
portStart: 8000
|
||||
portStep: 10
|
||||
- name: frontend
|
||||
portEnv: FRONTEND_PORT
|
||||
portStart: 3000
|
||||
portStep: 10
|
||||
|
||||
profiles:
|
||||
default:
|
||||
name: default
|
||||
runtime: host
|
||||
envPassthrough: []
|
||||
panes:
|
||||
- id: agent
|
||||
kind: agent
|
||||
focus: true
|
||||
- id: backend
|
||||
kind: command
|
||||
split: right
|
||||
command: ROOT="$(git rev-parse --show-toplevel)"; [ -f "$ROOT/.env.local" ] && source "$ROOT/.env.local"; cd "$ROOT/backend" && PORT=${BACKEND_PORT:-8000} cargo watch -x "run ${CARGO_FEATURES:+--features $CARGO_FEATURES}"
|
||||
- id: frontend
|
||||
kind: command
|
||||
split: bottom
|
||||
command: ROOT="$(git rev-parse --show-toplevel)"; [ -f "$ROOT/.env.local" ] && source "$ROOT/.env.local"; cd "$ROOT/frontend" && npm run generate-backend-client && REMOTE=${REMOTE:-http://localhost:${BACKEND_PORT:-8000}} npm run dev -- --port ${FRONTEND_PORT:-3000} --host 0.0.0.0
|
||||
|
||||
sandbox:
|
||||
name: sandbox
|
||||
image: windmill-sandbox
|
||||
envPassthrough:
|
||||
- AWS_ACCESS_KEY_ID
|
||||
- AWS_SECRET_ACCESS_KEY
|
||||
- R2_ENDPOINT
|
||||
- R2_BUCKET
|
||||
- R2_PUBLIC_URL
|
||||
extraMounts:
|
||||
- hostPath: ~/.ssh
|
||||
guestPath: /root/.ssh
|
||||
writable: true
|
||||
- hostPath: ~/.codex
|
||||
guestPath: /root/.codex
|
||||
writable: true
|
||||
- hostPath: ~/windmill-ee-private
|
||||
writable: true
|
||||
- hostPath: ~/windmill-ee-private__worktrees
|
||||
writable: true
|
||||
systemPrompt: >
|
||||
You are running inside a sandboxed container with full permissions.
|
||||
This worktree is configured with the following ports:
|
||||
|
||||
- Backend: port ${BACKEND_PORT}.
|
||||
Start with: cd backend && PORT=${BACKEND_PORT}
|
||||
DATABASE_URL=postgres://postgres:changeme@localhost:5432/windmill
|
||||
cargo watch -x run
|
||||
|
||||
- Frontend: port ${FRONTEND_PORT}.
|
||||
Start with: cd frontend && REMOTE=http://localhost:${BACKEND_PORT}
|
||||
npm run dev -- --port ${FRONTEND_PORT} --host 0.0.0.0
|
||||
|
||||
--- Screenshots ---
|
||||
You can take screenshots of the frontend UI and upload them to R2
|
||||
for use in PR descriptions.
|
||||
1) Take a screenshot:
|
||||
bunx playwright screenshot --browser chromium
|
||||
http://localhost:${FRONTEND_PORT}/path/to/page /tmp/screenshot.png
|
||||
2) Upload to R2:
|
||||
aws s3 cp /tmp/screenshot.png
|
||||
"s3://$(printenv R2_BUCKET)/$(git rev-parse --abbrev-ref HEAD)/screenshot.png"
|
||||
--endpoint-url "$(printenv R2_ENDPOINT)"
|
||||
3) The public URL will be:
|
||||
$(printenv R2_PUBLIC_URL)/<branch>/screenshot.png
|
||||
4) Include in PR descriptions using markdown image syntax.
|
||||
|
||||
--- Terminal Recordings (asciinema) ---
|
||||
You can record terminal sessions and upload them for sharing.
|
||||
asciinema is available on PATH.
|
||||
|
||||
1) Write a shell script with the commands to demo. Add sleep
|
||||
delays for readable pacing:
|
||||
- 0.5s after printing a "$ command" line (lets viewer read it)
|
||||
- 1.5-2s after command output (lets viewer absorb the result)
|
||||
- Set GIT_PAGER=cat and PAGER=cat to prevent pager hangs
|
||||
|
||||
2) Record headlessly:
|
||||
asciinema rec --headless --overwrite \
|
||||
-c "bash /tmp/demo.sh" \
|
||||
--window-size 120x50 \
|
||||
--title "Description of demo" \
|
||||
/tmp/demo.cast
|
||||
|
||||
3) Upload to asciinema.org:
|
||||
XDG_DATA_HOME=/tmp/.local/share \
|
||||
asciinema upload --server-url https://asciinema.org /tmp/demo.cast
|
||||
|
||||
--- Mermaid Diagrams ---
|
||||
You can render Mermaid diagrams to SVG using the pre-installed mmdc CLI.
|
||||
The puppeteer config (no-sandbox + Chromium path) is at /root/.puppeteerrc.json.
|
||||
|
||||
1) Write a .mmd file with your diagram:
|
||||
cat > /tmp/diagram.mmd << 'EOF'
|
||||
graph TD
|
||||
A[Start] --> B[End]
|
||||
EOF
|
||||
|
||||
2) Render to SVG (the -p flag is required):
|
||||
mmdc -i /tmp/diagram.mmd -o /tmp/diagram.svg -p /root/.puppeteerrc.json
|
||||
|
||||
3) Upload to R2:
|
||||
aws s3 cp /tmp/diagram.svg
|
||||
"s3://$(printenv R2_BUCKET)/$(git rev-parse --abbrev-ref HEAD)/diagram.svg"
|
||||
--endpoint-url "$(printenv R2_ENDPOINT)"
|
||||
|
||||
4) The public URL will be:
|
||||
$(printenv R2_PUBLIC_URL)/<branch>/diagram.svg
|
||||
|
||||
5) Include in PR descriptions using markdown image syntax.
|
||||
|
||||
IMPORTANT: Read docs/autonomous-mode.md before starting any work.
|
||||
|
||||
linkedRepos:
|
||||
- repo: windmill-labs/windmill-ee-private
|
||||
alias: ee
|
||||
integrations:
|
||||
github:
|
||||
linkedRepos: []
|
||||
linear:
|
||||
enabled: true
|
||||
|
||||
25
scripts/post-create.sh
Executable file
25
scripts/post-create.sh
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/worktree-common.sh"
|
||||
|
||||
backend_port="${BACKEND_PORT:-}"
|
||||
frontend_port="${FRONTEND_PORT:-}"
|
||||
|
||||
if [[ -z "$backend_port" || -z "$frontend_port" ]]; then
|
||||
echo "Missing BACKEND_PORT or FRONTEND_PORT in hook environment" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
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"
|
||||
wm_shared_post_create "$(pwd)"
|
||||
7
scripts/pre-remove.sh
Executable file
7
scripts/pre-remove.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/worktree-common.sh"
|
||||
|
||||
wm_kill_processes_from_env_file "$(pwd)/.env.local"
|
||||
wm_shared_pre_remove "$(pwd)"
|
||||
@@ -1,73 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Use WM_WORKTREE_PATH (set by workmux) so this works regardless of cwd
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/worktree-common.sh"
|
||||
|
||||
wt_dir="${WM_WORKTREE_PATH:-.}"
|
||||
echo "[cleanup] cwd=$(pwd) WM_WORKTREE_PATH=${WM_WORKTREE_PATH:-<unset>} wt_dir=$wt_dir"
|
||||
|
||||
# Kill backend/frontend processes using this worktree's ports
|
||||
if [ -f "$wt_dir/.env.local" ]; then
|
||||
source "$wt_dir/.env.local"
|
||||
echo "[cleanup] .env.local found: BACKEND_PORT=${BACKEND_PORT:-<unset>} FRONTEND_PORT=${FRONTEND_PORT:-<unset>}"
|
||||
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 "[cleanup] Killed process $pid on port $port" \
|
||||
|| echo "[cleanup] Warning: Could not kill process $pid on port $port"
|
||||
else
|
||||
echo "[cleanup] No process listening on port $port"
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "[cleanup] No .env.local at $wt_dir/.env.local"
|
||||
fi
|
||||
|
||||
# Drop per-worktree database
|
||||
if [ -n "${WM_DB_NAME:-}" ]; then
|
||||
db_conn="postgres://postgres:changeme@127.0.0.1:5432"
|
||||
if command -v psql &>/dev/null; then
|
||||
psql "$db_conn/postgres" -c "DROP DATABASE IF EXISTS ${WM_DB_NAME} WITH (FORCE)" 2>/dev/null \
|
||||
&& echo "[cleanup] Dropped database $WM_DB_NAME" \
|
||||
|| echo "[cleanup] Warning: Could not drop database $WM_DB_NAME"
|
||||
else
|
||||
echo "[cleanup] psql not found, skipping database cleanup for $WM_DB_NAME"
|
||||
fi
|
||||
else
|
||||
echo "[cleanup] No WM_DB_NAME in .env.local, skipping database cleanup"
|
||||
fi
|
||||
|
||||
# Remove the matching windmill-ee-private worktree if one exists
|
||||
wt_basename=$(basename "$wt_dir")
|
||||
|
||||
# Find ee repo using same discovery logic as worktree-env
|
||||
main_repo_root="$(cd "$(git -C "$wt_dir" rev-parse --git-common-dir 2>/dev/null)/.." && pwd)"
|
||||
parent_dir="$(cd "$wt_dir/.." && pwd)"
|
||||
echo "[cleanup] wt_basename=$wt_basename main_repo_root=$main_repo_root parent_dir=$parent_dir"
|
||||
|
||||
ee_repo=""
|
||||
for candidate in \
|
||||
"${main_repo_root:+${main_repo_root}/../windmill-ee-private}" \
|
||||
"${parent_dir}/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 [ -z "$ee_repo" ]; then
|
||||
echo "[cleanup] Could not find windmill-ee-private repo, skipping EE worktree cleanup"
|
||||
fi
|
||||
|
||||
ee_worktree_dir="${ee_repo:+${ee_repo}__worktrees/${wt_basename}}"
|
||||
echo "[cleanup] ee_repo=${ee_repo:-<not found>} ee_worktree_dir=${ee_worktree_dir:-<none>} exists=$([ -n "$ee_worktree_dir" ] && [ -d "$ee_worktree_dir" ] && echo yes || echo no)"
|
||||
if [ -n "$ee_worktree_dir" ] && [ -d "$ee_worktree_dir" ]; then
|
||||
git -C "$ee_repo" worktree remove "$ee_worktree_dir" --force 2>/dev/null \
|
||||
&& echo "[cleanup] Removed EE worktree at $ee_worktree_dir" \
|
||||
|| echo "[cleanup] Warning: Could not remove EE worktree at $ee_worktree_dir"
|
||||
fi
|
||||
|
||||
# Clean up Cursor grouped tmux session
|
||||
tmux kill-session -t "cursor-${wt_basename}" 2>/dev/null || true
|
||||
wm_kill_processes_from_env_file "${wt_dir}/.env.local"
|
||||
wm_shared_pre_remove "$wt_dir"
|
||||
|
||||
265
scripts/worktree-common.sh
Executable file
265
scripts/worktree-common.sh
Executable file
@@ -0,0 +1,265 @@
|
||||
#!/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
|
||||
}
|
||||
|
||||
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 [[ -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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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_setup_database "$repo_root" "${repo_root}/.env.local"
|
||||
wm_copy_dependencies "$repo_root" "$main_repo_root"
|
||||
wm_allow_direnv "$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
|
||||
|
||||
tmux kill-session -t "cursor-${wt_basename}" 2>/dev/null || true
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/worktree-common.sh"
|
||||
|
||||
port_in_use() {
|
||||
lsof -nP -iTCP:"$1" -sTCP:LISTEN &>/dev/null
|
||||
}
|
||||
@@ -51,154 +53,4 @@ if [[ -n "${CARGO_FEATURES:-}" ]]; then
|
||||
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
|
||||
wm_shared_post_create "$(pwd)"
|
||||
|
||||
Reference in New Issue
Block a user