chore(repo): reinitialize repository
This commit is contained in:
Executable
+266
@@ -0,0 +1,266 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
TMP_DIR="$(cd /tmp && pwd -P)"
|
||||
|
||||
BACKEND_PORT="${BACKEND_PORT:-3000}"
|
||||
FRONTEND_PORT="${FRONTEND_PORT:-5173}"
|
||||
FRONTEND_HOST="${FRONTEND_HOST:-127.0.0.1}"
|
||||
|
||||
BACKEND_BIN="${TMP_DIR}/inbox-host"
|
||||
BACKEND_BUILD_TARGET="./cmd/inbox"
|
||||
BACKEND_LOG="${TMP_DIR}/ai-workflow-v2-inbox.log"
|
||||
FRONTEND_LOG="${TMP_DIR}/ai-workflow-v2-dashboard.log"
|
||||
FRONTEND_BIN="${REPO_ROOT}/dashboard/node_modules/.bin/vite"
|
||||
GO_BUILD_CACHE="${TMP_DIR}/ai-workflow-v2-go-build"
|
||||
WORKSPACES_DIR="${REPO_ROOT}/inbox-worktrees"
|
||||
|
||||
require_cmd() {
|
||||
if ! command -v "$1" >/dev/null 2>&1; then
|
||||
echo "error: missing required command: $1" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
start_detached() {
|
||||
local cwd="$1"
|
||||
local log_file="$2"
|
||||
shift 2
|
||||
|
||||
LAUNCH_CWD="$cwd" LAUNCH_LOG="$log_file" python3 - "$@" <<'PY'
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
cwd = os.environ["LAUNCH_CWD"]
|
||||
log_path = os.environ["LAUNCH_LOG"]
|
||||
argv = sys.argv[1:]
|
||||
|
||||
with open(log_path, "ab", buffering=0) as log:
|
||||
proc = subprocess.Popen(
|
||||
argv,
|
||||
cwd=cwd,
|
||||
env=os.environ.copy(),
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=log,
|
||||
stderr=subprocess.STDOUT,
|
||||
start_new_session=True,
|
||||
)
|
||||
print(proc.pid)
|
||||
PY
|
||||
}
|
||||
|
||||
listening_pid() {
|
||||
local port="$1"
|
||||
lsof -tiTCP:"${port}" -sTCP:LISTEN 2>/dev/null | head -n 1 || true
|
||||
}
|
||||
|
||||
cwd_for_pid() {
|
||||
local pid="$1"
|
||||
lsof -a -p "$pid" -d cwd -Fn 2>/dev/null | sed -n 's/^n//p' | head -n 1
|
||||
}
|
||||
|
||||
txt_for_pid() {
|
||||
local pid="$1"
|
||||
lsof -a -p "$pid" -d txt -Fn 2>/dev/null | sed -n 's/^n//p' | head -n 1
|
||||
}
|
||||
|
||||
wait_for_port_to_close() {
|
||||
local port="$1"
|
||||
for _ in $(seq 1 40); do
|
||||
if [ -z "$(listening_pid "$port")" ]; then
|
||||
return 0
|
||||
fi
|
||||
sleep 0.25
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
process_exists() {
|
||||
local pid="$1"
|
||||
lsof -p "$pid" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
send_signal() {
|
||||
local signal="$1"
|
||||
local pid="$2"
|
||||
local label="$3"
|
||||
local port="$4"
|
||||
local err=""
|
||||
|
||||
if ! err="$(kill "$signal" "$pid" 2>&1)"; then
|
||||
if [ -z "$(listening_pid "$port")" ]; then
|
||||
return 0
|
||||
fi
|
||||
echo "error: failed to send ${signal} to ${label} (pid ${pid}): ${err}" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
stop_known_process() {
|
||||
local pid="$1"
|
||||
local label="$2"
|
||||
local port="$3"
|
||||
|
||||
echo "stopping ${label} (pid ${pid})..."
|
||||
send_signal "-TERM" "$pid" "$label" "$port"
|
||||
if wait_for_port_to_close "$port"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "${label} did not exit in time, forcing stop..."
|
||||
send_signal "-KILL" "$pid" "$label" "$port"
|
||||
if wait_for_port_to_close "$port"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "error: ${label} is still listening on port ${port} after SIGKILL." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
ensure_backend_port_ready() {
|
||||
local pid
|
||||
pid="$(listening_pid "$BACKEND_PORT")"
|
||||
if [ -z "$pid" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local cwd
|
||||
local txt
|
||||
cwd="$(cwd_for_pid "$pid")"
|
||||
txt="$(txt_for_pid "$pid")"
|
||||
if [ "$cwd" = "$REPO_ROOT" ] && [ "$txt" = "$BACKEND_BIN" ]; then
|
||||
stop_known_process "$pid" "backend" "$BACKEND_PORT"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "error: port ${BACKEND_PORT} is occupied by another process." >&2
|
||||
echo "pid: ${pid}" >&2
|
||||
echo "cwd: ${cwd}" >&2
|
||||
echo "exe: ${txt}" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
ensure_frontend_port_ready() {
|
||||
local pid
|
||||
pid="$(listening_pid "$FRONTEND_PORT")"
|
||||
if [ -z "$pid" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local cwd
|
||||
cwd="$(cwd_for_pid "$pid")"
|
||||
if [ "$cwd" = "${REPO_ROOT}/dashboard" ]; then
|
||||
stop_known_process "$pid" "frontend" "$FRONTEND_PORT"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local txt
|
||||
txt="$(txt_for_pid "$pid")"
|
||||
echo "error: port ${FRONTEND_PORT} is occupied by another process." >&2
|
||||
echo "pid: ${pid}" >&2
|
||||
echo "cwd: ${cwd}" >&2
|
||||
echo "exe: ${txt}" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
wait_for_http() {
|
||||
local url="$1"
|
||||
local label="$2"
|
||||
local log_file="$3"
|
||||
local pid="$4"
|
||||
|
||||
for _ in $(seq 1 60); do
|
||||
if curl -fsS "$url" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
if ! process_exists "$pid"; then
|
||||
echo "error: ${label} exited before becoming ready (pid ${pid})." >&2
|
||||
if [ -f "$log_file" ]; then
|
||||
echo "--- ${label} log tail ---" >&2
|
||||
tail -n 40 "$log_file" >&2 || true
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
sleep 0.5
|
||||
done
|
||||
|
||||
echo "error: ${label} failed to become ready: ${url}" >&2
|
||||
if [ -f "$log_file" ]; then
|
||||
echo "--- ${label} log tail ---" >&2
|
||||
tail -n 40 "$log_file" >&2 || true
|
||||
fi
|
||||
exit 1
|
||||
}
|
||||
|
||||
start_backend() {
|
||||
: > "$BACKEND_LOG"
|
||||
start_detached "$REPO_ROOT" "$BACKEND_LOG" \
|
||||
"$BACKEND_BIN" server \
|
||||
--workspaces-dir "$WORKSPACES_DIR" \
|
||||
--port "$BACKEND_PORT"
|
||||
}
|
||||
|
||||
start_frontend() {
|
||||
: > "$FRONTEND_LOG"
|
||||
start_detached "${REPO_ROOT}/dashboard" "$FRONTEND_LOG" \
|
||||
"$FRONTEND_BIN" --host "$FRONTEND_HOST" --port "$FRONTEND_PORT"
|
||||
}
|
||||
|
||||
backend_pid_summary() {
|
||||
listening_pid "$BACKEND_PORT"
|
||||
}
|
||||
|
||||
frontend_pid_summary() {
|
||||
listening_pid "$FRONTEND_PORT"
|
||||
}
|
||||
|
||||
main() {
|
||||
require_cmd go
|
||||
require_cmd lsof
|
||||
require_cmd curl
|
||||
require_cmd python3
|
||||
if [ ! -x "$FRONTEND_BIN" ]; then
|
||||
echo "error: missing dashboard dependencies at ${FRONTEND_BIN}" >&2
|
||||
echo "run: cd ${REPO_ROOT}/dashboard && npm install" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "building backend binary..."
|
||||
mkdir -p "$GO_BUILD_CACHE"
|
||||
mkdir -p "$WORKSPACES_DIR"
|
||||
(
|
||||
cd "${REPO_ROOT}/inbox"
|
||||
GOCACHE="$GO_BUILD_CACHE" go build -o "$BACKEND_BIN" "$BACKEND_BUILD_TARGET"
|
||||
)
|
||||
if [ ! -x "$BACKEND_BIN" ]; then
|
||||
echo "error: built backend binary is not executable: ${BACKEND_BIN}" >&2
|
||||
file "$BACKEND_BIN" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ensure_backend_port_ready
|
||||
ensure_frontend_port_ready
|
||||
|
||||
echo "starting backend on http://127.0.0.1:${BACKEND_PORT} ..."
|
||||
local backend_pid
|
||||
backend_pid="$(start_backend)"
|
||||
wait_for_http "http://127.0.0.1:${BACKEND_PORT}/api/v2/workspaces" "backend" "$BACKEND_LOG" "$backend_pid"
|
||||
|
||||
echo "starting frontend on http://${FRONTEND_HOST}:${FRONTEND_PORT} ..."
|
||||
local frontend_pid
|
||||
frontend_pid="$(start_frontend)"
|
||||
wait_for_http "http://${FRONTEND_HOST}:${FRONTEND_PORT}" "frontend" "$FRONTEND_LOG" "$frontend_pid"
|
||||
|
||||
echo ""
|
||||
echo "project restarted"
|
||||
echo "backend: http://127.0.0.1:${BACKEND_PORT} (pid $(backend_pid_summary))"
|
||||
echo "frontend: http://${FRONTEND_HOST}:${FRONTEND_PORT} (pid $(frontend_pid_summary))"
|
||||
echo "logs:"
|
||||
echo " ${BACKEND_LOG}"
|
||||
echo " ${FRONTEND_LOG}"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user