chore(repo): reinitialize repository

This commit is contained in:
2026-03-18 11:29:54 +08:00
commit 24871e213a
288 changed files with 44369 additions and 0 deletions
+266
View File
@@ -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 "$@"