-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstart.sh
More file actions
executable file
·236 lines (205 loc) · 6.34 KB
/
start.sh
File metadata and controls
executable file
·236 lines (205 loc) · 6.34 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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
cd "$ROOT_DIR"
MODE="${MODE:-prod}"
PORT="${PORT:-3000}"
FRONTEND_PORT="${FRONTEND_PORT:-5173}"
HOST="${HOST:-127.0.0.1}"
NODE_ENV="${NODE_ENV:-development}"
DATA_DIR="${DATA_DIR:-../data}"
FRONTEND_DIR="${FRONTEND_DIR:-../frontend/dist}"
JWT_ACCESS_SECRET="${JWT_ACCESS_SECRET:-development-access-secret-with-at-least-thirty-two-characters}"
JWT_REFRESH_SECRET="${JWT_REFRESH_SECRET:-development-refresh-secret-with-at-least-thirty-two-characters}"
PAY_CALLBACK_SECRET="${PAY_CALLBACK_SECRET:-development-payment-secret-with-at-least-thirty-two-characters}"
CORS_ORIGIN="${CORS_ORIGIN:-http://localhost:${PORT},http://127.0.0.1:${PORT},http://localhost:${FRONTEND_PORT},http://127.0.0.1:${FRONTEND_PORT}}"
usage() {
cat <<'USAGE'
HiCAD startup script
Usage:
./start.sh Build and start production-style backend on 127.0.0.1:3000
MODE=dev ./start.sh Start shared/backend/frontend dev servers
PORT=3100 ./start.sh Start backend on a custom port
MODE=dev FRONTEND_PORT=5174 ./start.sh
Start frontend dev server on a custom port
HICAD_AUTO_KILL_OWN_PORTS=0 ./start.sh
Disable automatic cleanup of previous HiCAD listeners
HICAD_KILL_PORT=1 ./start.sh
Force-kill any process listening on the selected port
HICAD_SKIP_PORT_CHECK=1 ./start.sh
Skip startup port preflight
Environment:
MODE=prod|dev
HOST=127.0.0.1
PORT=3000
FRONTEND_PORT=5173
NODE_ENV=development
DATA_DIR=../data
FRONTEND_DIR=../frontend/dist
USAGE
}
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
usage
exit 0
fi
need_command() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "Missing required command: $1" >&2
exit 1
fi
}
port_pids() {
local port="$1"
lsof -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null || true
}
process_cwd() {
local pid="$1"
lsof -a -p "$pid" -d cwd -Fn 2>/dev/null | sed -n 's/^n//p' | head -n 1
}
process_command() {
local pid="$1"
ps -p "$pid" -o command= 2>/dev/null || true
}
is_own_hicad_listener() {
local pid="$1"
local cwd
local command
cwd="$(process_cwd "$pid")"
command="$(process_command "$pid")"
if [[ "$cwd" == "$ROOT_DIR/backend" ]]; then
[[ "$command" == *node* || "$command" == *tsx* || "$command" == *nest* ]]
return
fi
if [[ "$cwd" == "$ROOT_DIR/frontend" ]]; then
[[ "$command" == *node* || "$command" == *vite* ]]
return
fi
return 1
}
all_listeners_are_own_hicad() {
local pids="$1"
local pid
local found=0
while IFS= read -r pid; do
[[ -z "$pid" ]] && continue
found=1
if ! is_own_hicad_listener "$pid"; then
return 1
fi
done <<<"$pids"
[[ "$found" == "1" ]]
}
wait_for_port_release() {
local port="$1"
local attempt
for attempt in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20; do
if [[ -z "$(port_pids "$port")" ]]; then
return 0
fi
sleep 0.2
done
echo "Port $port is still in use after stopping the previous listener." >&2
echo "Inspect it with: lsof -nP -iTCP:$port -sTCP:LISTEN" >&2
exit 1
}
stop_port_listeners() {
local port="$1"
local pids="$2"
local reason="$3"
local pid
while IFS= read -r pid; do
[[ -z "$pid" ]] && continue
echo "Stopping PID $pid on port $port ($reason)"
kill "$pid" 2>/dev/null || true
done <<<"$pids"
wait_for_port_release "$port"
}
check_port() {
local port="$1"
local label="$2"
local pids
pids="$(port_pids "$port")"
if [[ -z "$pids" ]]; then
return 0
fi
echo "Port $port ($label) is already in use by PID(s): $pids" >&2
lsof -nP -iTCP:"$port" -sTCP:LISTEN >&2 || true
if [[ "${HICAD_KILL_PORT:-0}" == "1" ]]; then
stop_port_listeners "$port" "$pids" "HICAD_KILL_PORT=1"
return 0
fi
if [[ "${HICAD_AUTO_KILL_OWN_PORTS:-1}" == "1" ]] && all_listeners_are_own_hicad "$pids"; then
stop_port_listeners "$port" "$pids" "previous HiCAD process from this repository"
return 0
fi
echo "Refusing to interfere with a process that does not look like this HiCAD checkout." >&2
suggest_port_command "$port" "$label"
echo "If you are sure, rerun with HICAD_KILL_PORT=1 to stop the listed PID(s)." >&2
exit 1
}
suggest_port_command() {
local occupied_port="$1"
local label="$2"
local suggested_port
suggested_port="$(find_free_port "$((occupied_port + 1))" || true)"
if [[ -z "$suggested_port" ]]; then
echo "No free port found near $occupied_port. Stop the listed process or set a different PORT manually." >&2
return 0
fi
if [[ "$label" == "frontend dev server" ]]; then
echo "Try: MODE=dev FRONTEND_PORT=$suggested_port ./start.sh" >&2
elif [[ "$MODE" == "dev" ]]; then
echo "Try: MODE=dev PORT=$suggested_port ./start.sh" >&2
else
echo "Try: PORT=$suggested_port ./start.sh" >&2
fi
}
find_free_port() {
local port="$1"
local end="$((port + 20))"
while (( port <= end )); do
if [[ -z "$(port_pids "$port")" ]]; then
echo "$port"
return 0
fi
port="$((port + 1))"
done
return 1
}
check_startup_ports() {
if [[ "${HICAD_SKIP_PORT_CHECK:-0}" == "1" ]]; then
echo "Skipping port preflight because HICAD_SKIP_PORT_CHECK=1"
return 0
fi
if [[ "$MODE" == "prod" ]]; then
check_port "$PORT" "HiCAD app"
elif [[ "$MODE" == "dev" ]]; then
check_port "$PORT" "backend API"
check_port "$FRONTEND_PORT" "frontend dev server"
fi
}
if [[ "$MODE" != "prod" && "$MODE" != "dev" ]]; then
echo "Unknown MODE: $MODE" >&2
usage
exit 1
fi
need_command lsof
check_startup_ports
need_command node
need_command pnpm
if [[ ! -d "$ROOT_DIR/node_modules" ]]; then
echo "Installing dependencies in $ROOT_DIR"
pnpm install
fi
if [[ "$MODE" == "dev" ]]; then
echo "Starting HiCAD in development mode"
export HOST PORT FRONTEND_PORT NODE_ENV DATA_DIR FRONTEND_DIR JWT_ACCESS_SECRET JWT_REFRESH_SECRET PAY_CALLBACK_SECRET CORS_ORIGIN
exec pnpm dev
fi
if [[ ! -f "$ROOT_DIR/backend/dist/main.js" || ! -f "$ROOT_DIR/frontend/dist/index.html" || "${HICAD_FORCE_BUILD:-0}" == "1" ]]; then
echo "Building HiCAD workspace"
pnpm build
fi
echo "Starting HiCAD at http://${HOST}:${PORT}"
export HOST PORT FRONTEND_PORT NODE_ENV DATA_DIR FRONTEND_DIR JWT_ACCESS_SECRET JWT_REFRESH_SECRET PAY_CALLBACK_SECRET CORS_ORIGIN
exec pnpm --filter @hicad/backend start