Files
multica/scripts/check.sh
Lambda e583c149ef fix(dev): make the local dev flow survive its own tooling
Three defects in the documented local flow, plus the bootstrap collapse
that keeps them from coming back.

1. `make daemon` produced a daemon that could not run any task. The chain
   ended in `go run`, and the daemon records its own executable path at
   startup to re-exec as the execution-environment helper for every task.
   `go run` deletes that temp binary when the launcher exits, so the
   daemon started, registered, heartbeat — then failed every task with
   "fork/exec .../go-build.../exe/multica: no such file or directory".
   `make daemon` now builds `server/bin/multica` and runs that (`ARGS`
   forwards any daemon subcommand), and `daemon start` refuses an
   ephemeral binary up front instead of deferring the failure.

2. `make stop` killed the daemon. `lsof -ti:PORT` matches every process
   holding a socket on the port, including CLIENTS — and the daemon holds
   a long-lived connection to the backend. Stop now goes through
   scripts/port-guard.sh, which filters to `-sTCP:LISTEN` and sends TERM
   before KILL. scripts/port-guard.test.sh pins both halves against a real
   listener and a real connected client.

3. Restarting over a running instance failed invisibly: the new process
   died on the bind, the old one kept serving, and /health answered 200
   throughout. `make dev` now preflights both ports and names the process
   that owns them, and /health reports `pid`, `started_at`, and `commit`
   so a caller can prove which process answered.

4. `make dev-bootstrap` collapses the ~12-step manual sequence into one
   command: env file, fixed verification code before first launch, DB via
   DATABASE_URL (not docker exec), port preflight, detached services,
   health verified against process identity, single-shot login, PAT,
   workspace, CLI profile, built daemon binary. It pins a durable TMPDIR
   so an agent-created environment outlives the run that created it.
   `make dev-bootstrap-stop` tears it down.

Measured on a clean checkout: 11.7s to a logged-in, daemon-attached
environment.

Co-authored-by: multica-agent <github@multica.ai>
2026-08-07 16:37:26 +08:00

138 lines
4.7 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# ==========================================================================
# Full verification pipeline: typecheck → unit tests → Go tests → E2E
# Usage: bash scripts/check.sh
# ==========================================================================
ENV_FILE="${ENV_FILE:-.env}"
if [ ! -f "$ENV_FILE" ]; then
echo "Missing env file: $ENV_FILE"
echo "Create .env from .env.example, or run 'make worktree-env' and use .env.worktree."
exit 1
fi
set -a
# shellcheck disable=SC1090
. "$ENV_FILE"
set +a
# shellcheck disable=SC1091
. scripts/local-env.sh
BACKEND_PID=""
FRONTEND_PID=""
STARTED_BACKEND=false
STARTED_FRONTEND=false
EXIT_CODE=0
# --------------------------------------------------------------------------
# Cleanup: kill only services this script started
# --------------------------------------------------------------------------
cleanup() {
echo ""
if [ "$STARTED_BACKEND" = true ] && [ -n "$BACKEND_PID" ]; then
kill "$BACKEND_PID" 2>/dev/null && wait "$BACKEND_PID" 2>/dev/null || true
echo " Stopped backend (PID $BACKEND_PID)"
fi
if [ "$STARTED_FRONTEND" = true ] && [ -n "$FRONTEND_PID" ]; then
kill "$FRONTEND_PID" 2>/dev/null && wait "$FRONTEND_PID" 2>/dev/null || true
echo " Stopped frontend (PID $FRONTEND_PID)"
fi
echo ""
if [ "$EXIT_CODE" -eq 0 ]; then
echo "✓ All checks passed."
else
echo "✗ Checks FAILED."
fi
exit "$EXIT_CODE"
}
trap cleanup EXIT
# --------------------------------------------------------------------------
# Utility: wait until a port responds
# --------------------------------------------------------------------------
wait_for_port() {
local port=$1 name=$2 max_wait=${3:-60} path=${4:-/}
local elapsed=0
echo " Waiting for $name on :$port..."
while ! curl -sf "http://localhost:${port}${path}" > /dev/null 2>&1; do
sleep 1
elapsed=$((elapsed + 1))
if [ "$elapsed" -ge "$max_wait" ]; then
echo " ERROR: $name did not start within ${max_wait}s"
EXIT_CODE=1
exit 1
fi
done
echo " $name ready (${elapsed}s)"
}
# --------------------------------------------------------------------------
# Step 0: Ensure DB
# --------------------------------------------------------------------------
echo "==> Using env file: $ENV_FILE"
echo "==> Verifying port guard (listener-only stop/preflight)..."
bash scripts/port-guard.test.sh || { EXIT_CODE=1; exit 1; }
echo "==> Checking PostgreSQL..."
bash scripts/ensure-postgres.sh "$ENV_FILE"
# --------------------------------------------------------------------------
# Step 1: TypeScript typecheck
# --------------------------------------------------------------------------
echo ""
echo "==> [1/5] TypeScript typecheck..."
pnpm typecheck || { EXIT_CODE=1; exit 1; }
# --------------------------------------------------------------------------
# Step 2: TypeScript unit tests (Vitest)
# --------------------------------------------------------------------------
echo ""
echo "==> [2/5] TypeScript unit tests..."
pnpm test || { EXIT_CODE=1; exit 1; }
# --------------------------------------------------------------------------
# Step 3: Go tests
# --------------------------------------------------------------------------
echo ""
echo "==> [3/5] Go tests..."
echo "==> Verifying Go test wrapper..."
bash scripts/test-go.test.sh || { EXIT_CODE=1; exit 1; }
echo "==> Running database migrations..."
(cd server && go run ./cmd/migrate up) || { EXIT_CODE=1; exit 1; }
bash scripts/test-go.sh || { EXIT_CODE=1; exit 1; }
# --------------------------------------------------------------------------
# Step 4: Start services for E2E (only if not already running)
# --------------------------------------------------------------------------
echo ""
echo "==> [4/5] Starting services for E2E..."
if curl -sf "http://localhost:${PORT}/health" > /dev/null 2>&1; then
echo " Backend already running on :$PORT"
else
echo " Starting backend..."
(cd server && go run ./cmd/server) > /tmp/multica-check-backend.log 2>&1 &
BACKEND_PID=$!
STARTED_BACKEND=true
wait_for_port "$PORT" "Backend" 90 "/health"
fi
if curl -sf "http://localhost:${FRONTEND_PORT}" > /dev/null 2>&1; then
echo " Frontend already running on :$FRONTEND_PORT"
else
echo " Starting frontend..."
pnpm dev:web > /tmp/multica-check-frontend.log 2>&1 &
FRONTEND_PID=$!
STARTED_FRONTEND=true
wait_for_port "$FRONTEND_PORT" "Frontend" 120 "/"
fi
# --------------------------------------------------------------------------
# Step 5: E2E tests (Playwright)
# --------------------------------------------------------------------------
echo ""
echo "==> [5/5] E2E tests (Playwright)..."
pnpm exec playwright test || { EXIT_CODE=1; exit 1; }