Files
treg/scripts/dev-local.sh
T
UncleCode 3ee1948c64 Merge origin/main into dev/hub: the tool hub, behind TREG_HUB_ENABLED
Brings main's 86 commits (dashboard boot and loading, legacy dashboard removal, test pruning,
overflow and routing fixes) together with the tool hub branch.

Conflicts, both sides kept unless noted:
- the legacy dashboard stays deleted, as on main;
- App.vue and the dashboard state: main's search page and loading states plus the hub pages;
- ci.yml: main's Postgres job, with the hub tests added to its list;
- dev-local.sh: main's server environment plus the hub flag passthrough;
- test_call_application_contract.py, test_marketplace_call.py: main's pruned files plus the
  hub branch's sync `settle: usage` test.

Not conflicts: main and the hub branch fixed the same CompanyEnrich empty-page billing; main's
rule runs first, so the hub branch's copy and its test are dropped. The Listing-tab test reads
the Vue source instead of the deleted legacy page.
2026-09-26 07:32:53 +08:00

140 lines
6.1 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# dev-local.sh — bring the treg dev stack up in one command.
#
# One service: the FastAPI server (`python -m treg`, port 18790) in a tmux window,
# started with TREG_EMAIL_DEV_MODE=true (the OTP shows in the login page / API
# response — no mail sender needed) and its own sqlite DB (treg-dev.db), so your
# real data and .env stay untouched.
#
# The `cli` subcommand runs the WORKING-TREE treg CLI in a sandboxed HOME
# (scripts/.dev-home), pre-pointed at localhost — it never touches
# ~/.treg/config.json, which usually points at production.
#
# Usage:
# scripts/dev-local.sh up # start the server (idempotent)
# TREG_HUB_ENABLED=1 …/dev-local.sh up # a feature flag from your shell reaches the server
# scripts/dev-local.sh down # stop it
# scripts/dev-local.sh status # window list + port check
# scripts/dev-local.sh logs # tail the server window
# scripts/dev-local.sh restart # restart the server window
# scripts/dev-local.sh attach # attach to the tmux session
# scripts/dev-local.sh cli <args…> # sandboxed dev CLI, e.g.: cli login
# scripts/dev-local.sh reset # down + wipe the dev DB and CLI sandbox
#
set -euo pipefail
SESSION="treg-dev"
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
PORT=18790
DEV_DB="${TREG_DEV_DB:-$ROOT/treg-dev.db}" # *.db is gitignored
DEV_HOME="$ROOT/scripts/.dev-home" # sandbox HOME for the dev CLI
DEV_KEYS="$DEV_HOME/dev-keys.env" # stable dev-only Fernet + session keys, minted once —
# without them every --reload restart drops sessions/secrets
# Feature flags from YOUR shell reach the server through here. A tmux session inherits the TMUX
# SERVER's environment, not the calling client's, so `TREG_HUB_ENABLED=1 dev-local.sh up` would
# otherwise start a server that never saw the flag: it looks like it worked, and every hub route
# answers 404. These expand HERE, in this script's own process, so the value is baked into the
# command string tmux runs. Add a flag to the list to pass it through.
PASSTHROUGH=""
for _v in TREG_HUB_ENABLED; do
[ -n "${!_v:-}" ] && PASSTHROUGH="$PASSTHROUGH $_v=${!_v}"
done
# This script's own three come LAST so they always win: the dev stack keeps its own sqlite database
# and its OTP dev mode whatever the caller's shell says.
SERVER_ENV="${PASSTHROUGH# } TREG_EMAIL_DEV_MODE=true TREG_FRONTEND_DEV=${TREG_FRONTEND_DEV:-true} TREG_PUBLIC_URL=http://localhost:$PORT TREG_CONNECT_DEMO_ENABLED=true TREG_DATABASE_URL=sqlite+aiosqlite:///$DEV_DB"
SERVER_CMD="cd $ROOT && set -a && . $DEV_KEYS && set +a && env $SERVER_ENV uv run python -m treg --reload"
ensure_dev_keys() {
[ -f "$DEV_KEYS" ] && return
mkdir -p "$DEV_HOME"
info "minting dev keys (once) → $DEV_KEYS"
{
printf 'TREG_SECRET_KEY=%s\n' "$(cd "$ROOT" && uv run python -m treg keygen)"
printf 'TREG_SESSION_SECRET=%s\n' "$(openssl rand -hex 24)"
} > "$DEV_KEYS"
}
c_reset=$'\033[0m'; c_grn=$'\033[32m'; c_ylw=$'\033[33m'; c_red=$'\033[31m'; c_cyn=$'\033[36m'
info() { printf "${c_cyn}▸ %s${c_reset}\n" "$*"; }
ok() { printf "${c_grn}✓ %s${c_reset}\n" "$*"; }
warn() { printf "${c_ylw}! %s${c_reset}\n" "$*"; }
die() { printf "${c_red}✗ %s${c_reset}\n" "$*" >&2; exit 1; }
port_up() { lsof -ti :"$1" -sTCP:LISTEN >/dev/null 2>&1; }
preflight() {
command -v tmux >/dev/null 2>&1 || die "tmux not found. Install: brew install tmux"
command -v uv >/dev/null 2>&1 || die "uv not found. Install: https://docs.astral.sh/uv/"
[ -d "$ROOT/.venv" ] || { info "no .venv — running uv sync"; (cd "$ROOT" && uv sync); }
}
up() {
preflight
ensure_dev_keys
command -v npm >/dev/null 2>&1 || die "npm not found. Install Node 22.12 or later."
if [ ! -d "$ROOT/frontend/node_modules" ]; then
(cd "$ROOT/frontend" && npm ci --include=dev)
fi
if port_up "$PORT" && ! tmux has-session -t "$SESSION" 2>/dev/null; then
die "port $PORT is already in use by something outside this script (lsof -i :$PORT)"
fi
if tmux has-session -t "$SESSION" 2>/dev/null; then
warn "session '$SESSION' already running"
else
tmux new-session -d -s "$SESSION" -n server "$SERVER_CMD"
info "waiting for the server…"
for _ in $(seq 1 30); do port_up "$PORT" && break; sleep 0.5; done
port_up "$PORT" || { tmux capture-pane -pt "$SESSION:server" | tail -20; die "server didn't come up — window output above"; }
fi
if ! tmux list-windows -t "$SESSION" -F '#{window_name}' | grep -qx frontend; then
port_up 5173 && die "port 5173 is already in use outside this dev stack"
tmux new-window -t "$SESSION" -n frontend "cd '$ROOT/frontend' && npm run dev -- --host localhost"
fi
ok "server up → http://localhost:$PORT (dashboard · /docs · /login)"
echo " email OTP dev mode is ON: codes appear on the page / in the API response"
echo " manual login test: scripts/dev-local.sh cli login"
}
down() {
tmux kill-session -t "$SESSION" 2>/dev/null && ok "session stopped" || warn "no session running"
}
status() {
if tmux has-session -t "$SESSION" 2>/dev/null; then
tmux list-windows -t "$SESSION" -F " window: #{window_name} (#{pane_current_command})"
else
warn "no tmux session '$SESSION'"
fi
if port_up "$PORT"; then ok "port $PORT listening"; else warn "port $PORT not listening"; fi
}
logs() { tmux capture-pane -pt "$SESSION:server" -S -200; }
restart() { ensure_dev_keys; tmux respawn-window -k -t "$SESSION:server" "$SERVER_CMD" && ok "server restarted"; }
attach() { tmux attach -t "$SESSION"; }
cli() {
# Working-tree CLI, sandboxed: HOME is swapped so ~/.treg/config.json (prod) is never touched.
mkdir -p "$DEV_HOME/.treg"
[ -f "$DEV_HOME/.treg/config.json" ] || printf '{"base_url":"http://localhost:%s"}\n' "$PORT" > "$DEV_HOME/.treg/config.json"
(cd "$ROOT" && HOME="$DEV_HOME" uv run treg "$@")
}
reset() {
down
rm -f "$DEV_DB" && rm -rf "$DEV_HOME"
ok "dev DB and CLI sandbox wiped"
}
case "${1:-}" in
up) up ;;
down) down ;;
status) status ;;
logs) logs ;;
restart) restart ;;
attach) attach ;;
cli) shift; cli "$@" ;;
reset) reset ;;
*) sed -n '3,24p' "$0"; exit 1 ;;
esac