Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 99 additions & 4 deletions scripts/dev.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,87 @@ set -euo pipefail
repo_root="$(cd "$(dirname "$0")/.." && pwd)"
cd "$repo_root"

state_dir="$repo_root/target/.claudette-dev"
pid_file="$state_dir/dev.pid"
mkdir -p "$state_dir"

collect_tree_pids() {
local pid="$1"
local child
echo "$pid"
while IFS= read -r child; do
[ -n "$child" ] || continue
collect_tree_pids "$child"
done < <(pgrep -P "$pid" 2>/dev/null || true)
}

kill_tree() {
local pid="$1"
local pids
pids="$(collect_tree_pids "$pid" | sort -rn | tr '\n' ' ')"
[ -n "$pids" ] || return
# shellcheck disable=SC2086
kill $pids 2>/dev/null || true
}

kill_tree_now() {
local pid="$1"
local pids
pids="$(collect_tree_pids "$pid" | sort -rn | tr '\n' ' ')"
[ -n "$pids" ] || return
# Give children one short chance to run their cleanup, then force the
# remaining process tree down. Ctrl+C in dev should return control
# immediately, not leave cargo-tauri/Vite/app children alive.
# shellcheck disable=SC2086
kill $pids 2>/dev/null || true
sleep 0.15
# shellcheck disable=SC2086
kill -9 $pids 2>/dev/null || true
}

is_expected_dev_pid() {
local pid="$1"
local command
command="$(ps -p "$pid" -o command= 2>/dev/null || true)"
case "$command" in
*"scripts/dev.sh"*|*"cargo tauri dev"*) return 0 ;;
*) return 1 ;;
esac
}

terminate_existing_dev() {
if [ ! -f "$pid_file" ]; then
return
fi
local old_pid
old_pid="$(cat "$pid_file" 2>/dev/null || true)"
if [ -z "$old_pid" ] || [ "$old_pid" = "$$" ]; then
rm -f "$pid_file"
return
fi
if ! kill -0 "$old_pid" 2>/dev/null; then
rm -f "$pid_file"
return
fi
if ! is_expected_dev_pid "$old_pid"; then
echo "▸ Ignoring stale dev pid file for unrelated process (pid $old_pid)"
rm -f "$pid_file"
return
fi

echo "▸ Stopping existing dev process tree (pid $old_pid)"
kill_tree "$old_pid"
for _ in {1..80}; do
if ! kill -0 "$old_pid" 2>/dev/null; then
rm -f "$pid_file"
return
fi
sleep 0.1
done
kill -9 "$old_pid" 2>/dev/null || true
rm -f "$pid_file"
}
Comment thread
jamesbrink marked this conversation as resolved.

find_free_port() {
local p=$1
while lsof -iTCP:"$p" -sTCP:LISTEN -n -P >/dev/null 2>&1; do
Expand All @@ -29,6 +110,9 @@ find_free_port() {
echo "$p"
}

terminate_existing_dev
echo "$$" >"$pid_file"

# Default Vite port is 14253 — deliberately moved off Tauri's stock 1420
# to avoid the cross-app dev-port hijack scenario where another Tauri
# starter template (which also defaults to 1420) launches and rebinds
Expand Down Expand Up @@ -67,8 +151,17 @@ with open(out, "w") as f:
}, f)
' "$discovery_file" "$$" "$debug_port" "$vite_port" "$started" "$cwd" "$branch"

cleanup() { rm -f "$discovery_file"; }
trap cleanup EXIT INT TERM
cleanup() {
rm -f "$discovery_file"
if [ -f "$pid_file" ] && [ "$(cat "$pid_file" 2>/dev/null || true)" = "$$" ]; then
rm -f "$pid_file"
fi
if [ -n "${cargo_pid:-}" ] && kill -0 "$cargo_pid" 2>/dev/null; then
kill_tree_now "$cargo_pid"
fi
}
trap cleanup EXIT
trap 'cleanup; exit 130' INT TERM

echo "▸ Branch: $branch"
echo "▸ Vite dev server: http://localhost:$vite_port"
Expand All @@ -83,6 +176,8 @@ if [[ "$(uname -s)" == "Darwin" ]]; then
runner_args=(--runner "$repo_root/scripts/macos-dev-app-runner.sh")
fi

exec cargo tauri dev --features "$features" \
cargo tauri dev --features "$features" \
"${runner_args[@]}" \
-c "{\"build\":{\"devUrl\":\"http://localhost:$vite_port\"}}"
-c "{\"build\":{\"devUrl\":\"http://localhost:$vite_port\"}}" &
cargo_pid=$!
wait "$cargo_pid"
101 changes: 94 additions & 7 deletions scripts/macos-dev-app-runner.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,34 @@ fi

repo_root="$(cd "$(dirname "$0")/.." && pwd)"

lock_dir="$repo_root/target/.claudette-dev-app-runner.lock"
lock_acquired=false
acquire_launch_lock() {
mkdir -p "$(dirname "$lock_dir")"
while ! mkdir "$lock_dir" 2>/dev/null; do
local lock_pid=""
if [ -f "$lock_dir/pid" ]; then
lock_pid="$(cat "$lock_dir/pid" 2>/dev/null || true)"
fi
if [ -z "$lock_pid" ] || ! kill -0 "$lock_pid" 2>/dev/null; then
rm -rf "$lock_dir" 2>/dev/null || true
continue
fi
sleep 0.1
done
echo "$$" >"$lock_dir/pid"
lock_acquired=true
}

release_launch_lock() {
if [ "$lock_acquired" = true ]; then
rm -rf "$lock_dir" 2>/dev/null || true
lock_acquired=false
fi
}

acquire_launch_lock

if [ -f "$1" ]; then
binary="$1"
shift
Expand Down Expand Up @@ -103,6 +131,43 @@ macos_dir="$contents_dir/MacOS"
resources_dir="$contents_dir/Resources"
bundle_executable="$macos_dir/claudette-app"

app_pids() {
pgrep -f -- "$bundle_executable" 2>/dev/null || true
}

kill_pids_now() {
local pids="$1"
[ -n "$pids" ] || return
# shellcheck disable=SC2086
kill $pids 2>/dev/null || true
sleep 0.15
# shellcheck disable=SC2086
kill -9 $pids 2>/dev/null || true
}

terminate_existing_app() {
local pids
pids="$(app_pids)"
if [ -z "$pids" ]; then
return
fi
echo "▸ Stopping existing Claudette Dev instance"
kill_pids_now "$pids"
for _ in {1..50}; do
if [ -z "$(app_pids)" ]; then
return
fi
sleep 0.1
done
pids="$(app_pids)"
if [ -n "$pids" ]; then
# shellcheck disable=SC2086
kill -9 $pids 2>/dev/null || true
fi
}

terminate_existing_app

mkdir -p "$macos_dir" "$resources_dir"
rm -f "$bundle_executable"
cp "$binary" "$bundle_executable"
Expand Down Expand Up @@ -160,14 +225,22 @@ stdout_fifo="$log_dir/stdout"
stderr_fifo="$log_dir/stderr"
mkfifo "$stdout_fifo" "$stderr_fifo"

terminate_started_app() {
if [ -n "${app_pid:-}" ] && kill -0 "$app_pid" 2>/dev/null; then
kill_pids_now "$app_pid"
fi
}

cleanup() {
release_launch_lock
rm -rf "$log_dir"
terminate_started_app
if [ -n "${open_pid:-}" ] && kill -0 "$open_pid" 2>/dev/null; then
Comment thread
jamesbrink marked this conversation as resolved.
Comment thread
jamesbrink marked this conversation as resolved.
kill "$open_pid" 2>/dev/null || true
fi
}
trap cleanup EXIT
trap 'kill $open_pid 2>/dev/null || true; exit 130' INT TERM
trap 'terminate_started_app; if [ -n "${open_pid:-}" ]; then kill "$open_pid" 2>/dev/null || true; kill -9 "$open_pid" 2>/dev/null || true; fi; exit 130' INT TERM

cat "$stdout_fifo" &
cat_stdout_pid=$!
Expand All @@ -182,11 +255,10 @@ for var in VITE_PORT CLAUDETTE_DEBUG_PORT CLAUDETTE_DEV_OVERRIDE RUST_LOG RUST_B
done

echo "▸ Launching $bundle_dir via Launch Services"
# `-n` matters for dev loops: the bundle identifier is stable, so Launch
# Services may otherwise activate an already-running Claudette Dev instance
# and return immediately. That makes cargo-tauri tear down Vite, which Bun
# reports as exit 143.
#
# The file lock above serializes overlapping DevCommand runners. Each runner
# first terminates the previous instance, then Launch Services starts exactly
# one fresh copy of this rebuilt bundle.
terminate_existing_app
# Build the open(1) argv incrementally rather than relying on
# `${arr[@]+"${arr[@]}"}` parameter-substitution tricks. Two reasons:
# (1) `set -u` makes naked `"${empty[@]}"` an "unbound variable" error
Expand All @@ -195,7 +267,7 @@ echo "▸ Launching $bundle_dir via Launch Services"
# (2) Building the array explicitly is unambiguous to the next reader —
# each element is properly quoted, no mental model of expansion
# required, and adding a future flag is just an `+=` append.
open_argv=(open -n -W -a "$bundle_dir" --stdout "$stdout_fifo" --stderr "$stderr_fifo")
open_argv=(open -W -n "$bundle_dir" --stdout "$stdout_fifo" --stderr "$stderr_fifo")
if [ "${#env_args[@]}" -gt 0 ]; then
open_argv+=("${env_args[@]}")
fi
Expand All @@ -206,6 +278,21 @@ fi
"${open_argv[@]}" &
open_pid=$!

for _ in {1..50}; do
app_pid="$(app_pids | tail -n 1)"
if [ -n "$app_pid" ]; then
break
fi
if ! kill -0 "$open_pid" 2>/dev/null; then
break
fi
sleep 0.1
done

# Let later hot-reload runner invocations proceed. They will terminate this
# specific app instance before launching their replacement.
release_launch_lock

wait "$open_pid"
exit_code=$?

Expand Down
Loading
Loading