-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Expand file tree
/
Copy pathdev.sh
More file actions
executable file
·203 lines (184 loc) · 6.78 KB
/
Copy pathdev.sh
File metadata and controls
executable file
·203 lines (184 loc) · 6.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
#!/usr/bin/env bash
# tickflow-stock-panel — 一键启动前后端
#
# 用法:
# ./dev.sh # 默认 backend:3018 frontend:3011
# BACKEND_PORT=8000 ./dev.sh # 改后端端口
# FRONTEND_PORT=5173 ./dev.sh # 改前端端口
#
# Ctrl-C 同时关闭两端。
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BACKEND_DIR="$ROOT/backend"
FRONTEND_DIR="$ROOT/frontend"
# Read only the launcher-owned keys from .env. Do not source the whole file:
# .env is data, not a shell script, and may contain values that are unsafe or
# invalid as Bash syntax. Exported environment variables keep highest priority.
read_dotenv_value() {
local key="$1"
if [[ ! -f "$ROOT/.env" ]]; then
return 0
fi
awk -v wanted="$key" '
$0 ~ "^[[:space:]]*" wanted "[[:space:]]*=" {
sub(/^[^=]*=/, "")
sub(/[[:space:]]+#.*$/, "")
gsub(/^[[:space:]]+|[[:space:]]+$/, "")
if (($0 ~ /^".*"$/) || ($0 ~ /^\047.*\047$/)) {
$0 = substr($0, 2, length($0) - 2)
}
print
exit
}
' "$ROOT/.env"
}
ENV_HOST="$(read_dotenv_value HOST)"
ENV_PORT="$(read_dotenv_value PORT)"
BACKEND_HOST="${HOST:-${ENV_HOST:-0.0.0.0}}"
# Keep BACKEND_PORT as a backwards-compatible explicit override.
BACKEND_PORT="${BACKEND_PORT:-${PORT:-${ENV_PORT:-3018}}}"
FRONTEND_PORT="${FRONTEND_PORT:-3011}"
UVICORN_ENV_ARGS=()
if [[ -f "$ROOT/.env" ]]; then
UVICORN_ENV_ARGS=(--env-file "$ROOT/.env")
fi
DISPLAY_HOST="$BACKEND_HOST"
if [[ "$DISPLAY_HOST" == "0.0.0.0" || "$DISPLAY_HOST" == "::" ]]; then
DISPLAY_HOST="localhost"
fi
# Match Docker's BACKEND_EXTRAS behavior so old CPUs can select Polars'
# rtcompat runtime before the backend starts. An exported value wins over .env.
if [[ -z "${BACKEND_EXTRAS+x}" && -f "$ROOT/.env" ]]; then
BACKEND_EXTRAS="$(read_dotenv_value BACKEND_EXTRAS)"
fi
BACKEND_EXTRAS="${BACKEND_EXTRAS:-}"
BACKEND_EXTRA_ARGS=()
if [[ -n "$BACKEND_EXTRAS" ]]; then
read -r -a backend_extras <<< "$BACKEND_EXTRAS"
for extra in "${backend_extras[@]}"; do
BACKEND_EXTRA_ARGS+=(--extra "$extra")
done
fi
BLUE='\033[0;34m'
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[0;33m'
GRAY='\033[0;90m'
NC='\033[0m'
info() { echo -e "${GRAY}[dev]${NC} $*"; }
ok() { echo -e "${GREEN}[dev]${NC} $*"; }
warn() { echo -e "${YELLOW}[dev]${NC} $*"; }
err() { echo -e "${RED}[dev]${NC} $*" >&2; }
# ===== 1. 依赖检查 =====
require_cmd() {
local cmd="$1" hint="$2"
if ! command -v "$cmd" >/dev/null 2>&1; then
err "$cmd 未安装"
echo " 安装方式:$hint"
exit 1
fi
}
require_cmd uv "curl -LsSf https://astral.sh/uv/install.sh | sh"
require_cmd pnpm "npm i -g pnpm 或 corepack enable && corepack prepare pnpm@9 --activate"
# ===== 2. 端口占用检查 —— 占用就直接 kill =====
free_port() {
local name="$1" port="$2"
local pids
pids=$(lsof -nP -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null || true)
if [ -z "$pids" ]; then
return 0
fi
warn "端口 $port($name)被占用,kill 现有进程 PID: $(echo "$pids" | xargs)"
# 先 TERM
echo "$pids" | xargs kill 2>/dev/null || true
sleep 1
# 还活着就 KILL
pids=$(lsof -nP -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null || true)
if [ -n "$pids" ]; then
warn "TERM 没杀掉,改用 KILL -9"
echo "$pids" | xargs kill -9 2>/dev/null || true
sleep 1
fi
# 再确认一次
pids=$(lsof -nP -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null || true)
if [ -n "$pids" ]; then
err "端口 $port 仍被占用 — kill 失败。请手动处理:lsof -i :$port"
exit 1
fi
ok "端口 $port 已释放"
}
free_port backend "$BACKEND_PORT"
free_port frontend "$FRONTEND_PORT"
# ===== 3. 依赖安装 =====
if [ ! -d "$BACKEND_DIR/.venv" ] || [ "${#BACKEND_EXTRA_ARGS[@]}" -gt 0 ]; then
if [ "${#BACKEND_EXTRA_ARGS[@]}" -gt 0 ]; then
info "同步后端 Python 依赖,extras: $BACKEND_EXTRAS"
else
info "后端首次启动 — 安装 Python 依赖(约 1-2 分钟)..."
fi
# macOS 自带 bash 3.2 在 set -u 下展开空数组会报 unbound variable,
# ${arr[@]+"${arr[@]}"} 守卫:数组为空时展开为零个参数,非空时逐个带引号展开。
( cd "$BACKEND_DIR" && uv sync --frozen ${BACKEND_EXTRA_ARGS[@]+"${BACKEND_EXTRA_ARGS[@]}"} )
ok "后端依赖装好了"
fi
if [ ! -d "$FRONTEND_DIR/node_modules" ]; then
info "前端首次启动 — 安装 Node 依赖..."
( cd "$FRONTEND_DIR" && pnpm install )
ok "前端依赖装好了"
fi
# ===== 4. 启动 + 日志前缀 =====
PIDS=()
cleanup() {
echo
info "关闭服务..."
for pid in "${PIDS[@]:-}"; do
if [ -n "$pid" ]; then
kill "$pid" 2>/dev/null || true
fi
done
# 等子进程退出,避免孤儿
wait 2>/dev/null || true
ok "已退出"
exit 0
}
trap cleanup INT TERM
# 用 awk 加前缀(macOS sed 没有 -u line-buffered,改用 awk + fflush 兼容)
prefix_awk() {
awk -v p="$1" '{ print p $0; fflush() }'
}
echo
echo -e "${BLUE}╭──────────────────────────────────────────────╮${NC}"
echo -e "${BLUE}│${NC} ${GREEN}tickflow-stock-panel${NC} ${BLUE}│${NC}"
echo -e "${BLUE}│${NC} ${BLUE}│${NC}"
echo -e "${BLUE}│${NC} backend ${YELLOW}http://$DISPLAY_HOST:$BACKEND_PORT${NC} ${BLUE}│${NC}"
echo -e "${BLUE}│${NC} frontend ${YELLOW}http://$DISPLAY_HOST:$FRONTEND_PORT${NC} ${BLUE}│${NC}"
echo -e "${BLUE}│${NC} ${BLUE}│${NC}"
echo -e "${BLUE}│${NC} Ctrl-C 同时关闭两端 ${BLUE}│${NC}"
echo -e "${BLUE}╰──────────────────────────────────────────────╯${NC}"
echo
(
cd "$BACKEND_DIR"
# --no-sync: 跳过依赖解析, 直接用已安装的 .venv。
# 比 --frozen 更彻底: 不校验 lockfile, 避免镜像源 403/网络抖动导致后端起不来。
# python -m uvicorn: 强制用 venv 的解释器和 uvicorn 模块, 防止 PATH 里
# 其他 Python(如 /usr/local/bin/uvicorn) 抢先, 导致用错误版本启动后端。
uv run --no-sync python -m uvicorn app.main:app ${UVICORN_ENV_ARGS[@]+"${UVICORN_ENV_ARGS[@]}"} --reload \
--host "$BACKEND_HOST" --port "$BACKEND_PORT" 2>&1 \
| prefix_awk "$(printf "${BLUE}[backend ]${NC} ")"
) &
PIDS+=("$!")
(
cd "$FRONTEND_DIR"
BACKEND_HOST="$BACKEND_HOST" BACKEND_PORT="$BACKEND_PORT" \
pnpm dev --host "$BACKEND_HOST" --port "$FRONTEND_PORT" 2>&1 \
| prefix_awk "$(printf "${GREEN}[frontend]${NC} ")"
) &
PIDS+=("$!")
# 等任一退出(bash 4.3+)或全部退出(老 bash)
if wait -n 2>/dev/null; then
warn "其中一个进程退出,正在关闭另一个..."
cleanup
else
# 老 bash 没有 wait -n,退化为 wait 全部
wait
fi