@lark-apaas/coding-template-nestjs-react-fullstack 0.1.47-alpha.20261002215903 → 0.1.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/template/package-lock.json +14013 -17816
- package/template/package.json +2 -3
- package/template/scripts/build.sh +17 -609
package/template/package.json
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"upgrade": "npx -y @lark-apaas/fullstack-cli sync --disable-gen-openapi"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@lark-apaas/fullstack-nestjs-core": "^1.1.
|
|
31
|
+
"@lark-apaas/fullstack-nestjs-core": "^1.1.65",
|
|
32
32
|
"@nestjs/axios": "^4.0.1",
|
|
33
33
|
"@nestjs/cache-manager": "^3.1.3",
|
|
34
34
|
"@nestjs/common": "^10.4.20",
|
|
@@ -52,9 +52,8 @@
|
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@hookform/resolvers": "^5.2.2",
|
|
55
|
-
"@lark-apaas/client-toolkit": "^1.2.
|
|
55
|
+
"@lark-apaas/client-toolkit": "^1.2.73",
|
|
56
56
|
"@lark-apaas/coding-preset-vite-react": "^1.0.25",
|
|
57
|
-
"@lark-apaas/fullstack-cli": "^1.1.67",
|
|
58
57
|
"@lark-apaas/fullstack-presets": "^1.1.25",
|
|
59
58
|
"@nestjs/cli": "^10.4.9",
|
|
60
59
|
"@radix-ui/react-accordion": "^1.2.12",
|
|
@@ -32,591 +32,6 @@ npm run gen:openapi
|
|
|
32
32
|
print_time $STEP_START
|
|
33
33
|
echo ""
|
|
34
34
|
|
|
35
|
-
# Acquire before the first dist mutation. The opaque token is inherited only by
|
|
36
|
-
# this wrapper's descendants, allowing the child Vite build to verify same-owner
|
|
37
|
-
# reentry without exposing a general conflict bypass.
|
|
38
|
-
OUTPUT_LEASE_ACQUIRED=0
|
|
39
|
-
OUTPUT_LEASE_HEARTBEAT_PID=""
|
|
40
|
-
OUTPUT_LEASE_HEARTBEAT_START=""
|
|
41
|
-
OUTPUT_LEASE_HEARTBEAT_LOG="/tmp/miaoda-output-lease-heartbeat-$$.log"
|
|
42
|
-
OUTPUT_JOB_PIDS=()
|
|
43
|
-
OUTPUT_JOB_PGIDS=()
|
|
44
|
-
OUTPUT_JOB_SIDS=()
|
|
45
|
-
OUTPUT_JOB_STARTS=()
|
|
46
|
-
OUTPUT_JOB_CONTROL_DIRS=()
|
|
47
|
-
OUTPUT_JOB_LAUNCHING=0
|
|
48
|
-
OUTPUT_PENDING_SIGNAL_STATUS=0
|
|
49
|
-
OUTPUT_PROCESS_STATE_UNKNOWN=0
|
|
50
|
-
|
|
51
|
-
process_identity() {
|
|
52
|
-
local pid=$1
|
|
53
|
-
if [[ -r "/proc/$pid/stat" ]]; then
|
|
54
|
-
sed 's/.*) //' "/proc/$pid/stat" | awk '{print $20, $3, $4, $1}'
|
|
55
|
-
else
|
|
56
|
-
local value pgid sid state start
|
|
57
|
-
value="$(ps -o pgid=,sid=,stat=,lstart= -p "$pid" 2>/dev/null)" || return 2
|
|
58
|
-
[[ -n "$value" ]] || return 1
|
|
59
|
-
read -r pgid sid state start <<< "$value"
|
|
60
|
-
echo "$start $pgid $sid $state"
|
|
61
|
-
fi
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
process_start_identity() {
|
|
65
|
-
local pid=$1
|
|
66
|
-
local start pgid sid state
|
|
67
|
-
if ! read -r start pgid sid state < <(process_identity "$pid"); then
|
|
68
|
-
return 1
|
|
69
|
-
fi
|
|
70
|
-
echo "$start"
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
session_live_pids() {
|
|
74
|
-
local sid=$1
|
|
75
|
-
local snapshot
|
|
76
|
-
snapshot="$(ps -eo pid=,sid=,stat=)" || return 2
|
|
77
|
-
awk -v sid="$sid" '$2 == sid && $3 !~ /^Z/ {print $1}' <<< "$snapshot"
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
descendant_live_pids() {
|
|
81
|
-
local root=$1
|
|
82
|
-
local known=" "
|
|
83
|
-
local previous=""
|
|
84
|
-
local snapshot pid ppid state
|
|
85
|
-
snapshot="$(ps -eo pid=,ppid=,stat=)" || return 2
|
|
86
|
-
while read -r pid ppid state; do
|
|
87
|
-
if [[ "$pid" == "$root" && "$state" != Z* ]]; then
|
|
88
|
-
known+="$pid "
|
|
89
|
-
break
|
|
90
|
-
fi
|
|
91
|
-
done <<< "$snapshot"
|
|
92
|
-
while [[ "$known" != "$previous" ]]; do
|
|
93
|
-
previous=$known
|
|
94
|
-
while read -r pid ppid state; do
|
|
95
|
-
[[ -z "$pid" || "$state" == Z* ]] && continue
|
|
96
|
-
if [[ "$known" == *" $ppid "* && "$known" != *" $pid "* ]]; then
|
|
97
|
-
known+="$pid "
|
|
98
|
-
fi
|
|
99
|
-
done <<< "$snapshot"
|
|
100
|
-
done
|
|
101
|
-
echo "$known"
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
collect_output_tree_pids() {
|
|
105
|
-
local root=$1
|
|
106
|
-
local sid=$2
|
|
107
|
-
local session_pids descendant_pids combined pid
|
|
108
|
-
session_pids="$(session_live_pids "$sid")" || return 2
|
|
109
|
-
combined=" ${session_pids//$'\n'/ } "
|
|
110
|
-
if [[ -n "$root" ]]; then
|
|
111
|
-
descendant_pids="$(descendant_live_pids "$root")" || return 2
|
|
112
|
-
for pid in $descendant_pids; do
|
|
113
|
-
[[ "$combined" == *" $pid "* ]] || combined+="$pid "
|
|
114
|
-
done
|
|
115
|
-
fi
|
|
116
|
-
echo "$combined"
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
register_output_job() {
|
|
120
|
-
OUTPUT_JOB_PIDS+=("$1")
|
|
121
|
-
OUTPUT_JOB_PGIDS+=("$2")
|
|
122
|
-
OUTPUT_JOB_SIDS+=("$3")
|
|
123
|
-
OUTPUT_JOB_STARTS+=("$4")
|
|
124
|
-
OUTPUT_JOB_CONTROL_DIRS+=("$5")
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
cleanup_output_job_control() {
|
|
128
|
-
local directory=$1
|
|
129
|
-
[[ -z "$directory" ]] && return
|
|
130
|
-
rm -f "$directory/start" "$directory/status" "$directory/status.tmp" "$directory/finish"
|
|
131
|
-
rmdir "$directory" >/dev/null 2>&1 || true
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
forget_output_job() {
|
|
135
|
-
local target=$1
|
|
136
|
-
local index
|
|
137
|
-
for index in "${!OUTPUT_JOB_PIDS[@]}"; do
|
|
138
|
-
if [[ "${OUTPUT_JOB_PIDS[$index]}" == "$target" ]]; then
|
|
139
|
-
OUTPUT_JOB_PIDS[$index]=""
|
|
140
|
-
OUTPUT_JOB_PGIDS[$index]=""
|
|
141
|
-
OUTPUT_JOB_SIDS[$index]=""
|
|
142
|
-
OUTPUT_JOB_STARTS[$index]=""
|
|
143
|
-
cleanup_output_job_control "${OUTPUT_JOB_CONTROL_DIRS[$index]}"
|
|
144
|
-
OUTPUT_JOB_CONTROL_DIRS[$index]=""
|
|
145
|
-
return
|
|
146
|
-
fi
|
|
147
|
-
done
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
finish_output_job_launch() {
|
|
151
|
-
OUTPUT_JOB_LAUNCHING=0
|
|
152
|
-
if [[ "$OUTPUT_PENDING_SIGNAL_STATUS" != "0" ]]; then
|
|
153
|
-
local pending=$OUTPUT_PENDING_SIGNAL_STATUS
|
|
154
|
-
OUTPUT_PENDING_SIGNAL_STATUS=0
|
|
155
|
-
exit "$pending"
|
|
156
|
-
fi
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
wait_for_tracked_pid_terminal() {
|
|
160
|
-
local pid=$1
|
|
161
|
-
local expected_start=$2
|
|
162
|
-
local attempts=${3:-100}
|
|
163
|
-
local attempt start pgid sid state
|
|
164
|
-
for attempt in $(seq 1 "$attempts"); do
|
|
165
|
-
if ! read -r start pgid sid state < <(process_identity "$pid"); then
|
|
166
|
-
if kill -0 "$pid" 2>/dev/null; then
|
|
167
|
-
return 2
|
|
168
|
-
fi
|
|
169
|
-
wait "$pid" >/dev/null 2>&1 || true
|
|
170
|
-
return 0
|
|
171
|
-
fi
|
|
172
|
-
if [[ -n "$expected_start" && "$start" != "$expected_start" ]]; then
|
|
173
|
-
return 2
|
|
174
|
-
fi
|
|
175
|
-
if [[ "$state" == Z* ]]; then
|
|
176
|
-
wait "$pid" >/dev/null 2>&1 || true
|
|
177
|
-
return 0
|
|
178
|
-
fi
|
|
179
|
-
sleep 0.05
|
|
180
|
-
done
|
|
181
|
-
return 1
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
rollback_unregistered_launch() {
|
|
185
|
-
local pid=$1
|
|
186
|
-
local start=$2
|
|
187
|
-
local pgid=$3
|
|
188
|
-
local sid=$4
|
|
189
|
-
local current_start current_pgid current_sid state
|
|
190
|
-
if read -r current_start current_pgid current_sid state < <(process_identity "$pid"); then
|
|
191
|
-
if [[ "$current_start" != "$start" || "$current_pgid" != "$pgid" || "$current_sid" != "$sid" ]]; then
|
|
192
|
-
return 2
|
|
193
|
-
fi
|
|
194
|
-
kill -TERM "$pid" >/dev/null 2>&1 || true
|
|
195
|
-
if ! wait_for_tracked_pid_terminal "$pid" "$start" 40; then
|
|
196
|
-
kill -KILL "$pid" >/dev/null 2>&1 || true
|
|
197
|
-
wait_for_tracked_pid_terminal "$pid" "$start" 40 || return 1
|
|
198
|
-
fi
|
|
199
|
-
elif ! kill -0 "$pid" 2>/dev/null; then
|
|
200
|
-
wait "$pid" >/dev/null 2>&1 || true
|
|
201
|
-
else
|
|
202
|
-
return 2
|
|
203
|
-
fi
|
|
204
|
-
return 0
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
start_output_job() {
|
|
208
|
-
local __pid_var=$1
|
|
209
|
-
local log_file=$2
|
|
210
|
-
shift 2
|
|
211
|
-
local pid pgid sid state start_identity attempt control_dir status_file
|
|
212
|
-
local handshake_complete=0 terminal_before_handshake=0
|
|
213
|
-
|
|
214
|
-
control_dir="$(mktemp -d "/tmp/miaoda-output-job-$$.XXXXXXXX")"
|
|
215
|
-
status_file="$control_dir/status"
|
|
216
|
-
OUTPUT_JOB_LAUNCHING=1
|
|
217
|
-
setsid --wait bash -c '
|
|
218
|
-
control=$1
|
|
219
|
-
shift
|
|
220
|
-
deadline=$((SECONDS + 3))
|
|
221
|
-
while [[ ! -f "$control/start" ]]; do
|
|
222
|
-
(( SECONDS >= deadline )) && exit 124
|
|
223
|
-
done
|
|
224
|
-
"$@" &
|
|
225
|
-
direct_pid=$!
|
|
226
|
-
direct_status=0
|
|
227
|
-
wait "$direct_pid" || direct_status=$?
|
|
228
|
-
printf "%s\n" "$direct_status" > "$control/status.tmp"
|
|
229
|
-
mv "$control/status.tmp" "$control/status"
|
|
230
|
-
while [[ ! -f "$control/finish" ]]; do :; done
|
|
231
|
-
exit "$direct_status"
|
|
232
|
-
' output-job-guardian "$control_dir" "$@" >"$log_file" 2>&1 &
|
|
233
|
-
pid=$!
|
|
234
|
-
|
|
235
|
-
for attempt in $(seq 1 100); do
|
|
236
|
-
if read -r start_identity pgid sid state < <(process_identity "$pid"); then
|
|
237
|
-
if [[ "$state" == Z* ]]; then
|
|
238
|
-
terminal_before_handshake=1
|
|
239
|
-
break
|
|
240
|
-
fi
|
|
241
|
-
if [[ -n "$start_identity" && "$pgid" == "$pid" && "$sid" == "$pid" ]]; then
|
|
242
|
-
handshake_complete=1
|
|
243
|
-
break
|
|
244
|
-
fi
|
|
245
|
-
elif ! kill -0 "$pid" 2>/dev/null; then
|
|
246
|
-
terminal_before_handshake=1
|
|
247
|
-
break
|
|
248
|
-
fi
|
|
249
|
-
sleep 0.01
|
|
250
|
-
done
|
|
251
|
-
|
|
252
|
-
if [[ "$handshake_complete" != "1" ]]; then
|
|
253
|
-
OUTPUT_PROCESS_STATE_UNKNOWN=1
|
|
254
|
-
if [[ "$terminal_before_handshake" == "1" ]]; then
|
|
255
|
-
wait_for_tracked_pid_terminal "$pid" "${start_identity:-}" 1 || true
|
|
256
|
-
elif [[ -n "${start_identity:-}" && -n "${pgid:-}" && -n "${sid:-}" ]]; then
|
|
257
|
-
rollback_unregistered_launch "$pid" "$start_identity" "$pgid" "$sid" || true
|
|
258
|
-
else
|
|
259
|
-
# The trusted guardian never opens the start gate and self-terminates in a
|
|
260
|
-
# bounded interval. Do not signal a process whose identity is unknown.
|
|
261
|
-
wait_for_tracked_pid_terminal "$pid" "" 80 || true
|
|
262
|
-
fi
|
|
263
|
-
cleanup_output_job_control "$control_dir"
|
|
264
|
-
echo "MIAODA_PREVIEW_STATE_UNKNOWN: output job launch handshake failed" >&2
|
|
265
|
-
finish_output_job_launch
|
|
266
|
-
return 1
|
|
267
|
-
fi
|
|
268
|
-
|
|
269
|
-
register_output_job "$pid" "$pgid" "$sid" "$start_identity" "$control_dir"
|
|
270
|
-
printf -v "$__pid_var" '%s' "$pid"
|
|
271
|
-
if [[ "$OUTPUT_PENDING_SIGNAL_STATUS" == "0" ]]; then
|
|
272
|
-
: > "$control_dir/start"
|
|
273
|
-
fi
|
|
274
|
-
finish_output_job_launch
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
find_output_job_index() {
|
|
278
|
-
local target=$1
|
|
279
|
-
local index
|
|
280
|
-
for index in "${!OUTPUT_JOB_PIDS[@]}"; do
|
|
281
|
-
if [[ "${OUTPUT_JOB_PIDS[$index]}" == "$target" ]]; then
|
|
282
|
-
echo "$index"
|
|
283
|
-
return 0
|
|
284
|
-
fi
|
|
285
|
-
done
|
|
286
|
-
return 1
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
wait_output_job() {
|
|
290
|
-
local pid=$1
|
|
291
|
-
local index control status_file status members member
|
|
292
|
-
index="$(find_output_job_index "$pid")" || {
|
|
293
|
-
OUTPUT_PROCESS_STATE_UNKNOWN=1
|
|
294
|
-
return 125
|
|
295
|
-
}
|
|
296
|
-
control="${OUTPUT_JOB_CONTROL_DIRS[$index]}"
|
|
297
|
-
status_file="$control/status"
|
|
298
|
-
|
|
299
|
-
# Wait for the guarded direct-command status, not guardian/session exit. A
|
|
300
|
-
# signal trap can interrupt this loop and cleanup still retains the record.
|
|
301
|
-
while [[ ! -f "$status_file" ]]; do
|
|
302
|
-
local current_start current_pgid current_sid current_state
|
|
303
|
-
if ! read -r current_start current_pgid current_sid current_state < <(process_identity "$pid"); then
|
|
304
|
-
OUTPUT_PROCESS_STATE_UNKNOWN=1
|
|
305
|
-
return 125
|
|
306
|
-
fi
|
|
307
|
-
if [[ "$current_start" != "${OUTPUT_JOB_STARTS[$index]}" || "$current_pgid" != "${OUTPUT_JOB_PGIDS[$index]}" || "$current_sid" != "${OUTPUT_JOB_SIDS[$index]}" || "$current_state" == Z* ]]; then
|
|
308
|
-
OUTPUT_PROCESS_STATE_UNKNOWN=1
|
|
309
|
-
return 125
|
|
310
|
-
fi
|
|
311
|
-
sleep 0.05
|
|
312
|
-
done
|
|
313
|
-
if ! status="$(cat "$status_file")"; then
|
|
314
|
-
OUTPUT_PROCESS_STATE_UNKNOWN=1
|
|
315
|
-
return 125
|
|
316
|
-
fi
|
|
317
|
-
if [[ ! "$status" =~ ^[0-9]+$ || "$status" -gt 255 ]]; then
|
|
318
|
-
OUTPUT_PROCESS_STATE_UNKNOWN=1
|
|
319
|
-
return 125
|
|
320
|
-
fi
|
|
321
|
-
|
|
322
|
-
if ! members="$(session_live_pids "${OUTPUT_JOB_SIDS[$index]}")"; then
|
|
323
|
-
OUTPUT_PROCESS_STATE_UNKNOWN=1
|
|
324
|
-
terminate_output_job "$index" || true
|
|
325
|
-
return 125
|
|
326
|
-
fi
|
|
327
|
-
for member in $members; do
|
|
328
|
-
if [[ "$member" != "$pid" ]]; then
|
|
329
|
-
# The direct command is terminal but an output descendant survived.
|
|
330
|
-
# Retain the session record and terminate the complete isolated tree.
|
|
331
|
-
terminate_output_job "$index" || {
|
|
332
|
-
OUTPUT_PROCESS_STATE_UNKNOWN=1
|
|
333
|
-
return 125
|
|
334
|
-
}
|
|
335
|
-
return "$status"
|
|
336
|
-
fi
|
|
337
|
-
done
|
|
338
|
-
|
|
339
|
-
: > "$control/finish"
|
|
340
|
-
if ! wait_for_tracked_pid_terminal "$pid" "${OUTPUT_JOB_STARTS[$index]}" 40; then
|
|
341
|
-
terminate_output_job "$index" || {
|
|
342
|
-
OUTPUT_PROCESS_STATE_UNKNOWN=1
|
|
343
|
-
return 125
|
|
344
|
-
}
|
|
345
|
-
else
|
|
346
|
-
forget_output_job "$pid"
|
|
347
|
-
fi
|
|
348
|
-
return "$status"
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
validated_group_is_alive() {
|
|
352
|
-
local pgid=$1
|
|
353
|
-
kill -0 -- "-$pgid" >/dev/null 2>&1
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
reap_tracked_root_if_terminal() {
|
|
357
|
-
local pid=$1
|
|
358
|
-
local expected_start=$2
|
|
359
|
-
local start pgid sid state
|
|
360
|
-
if read -r start pgid sid state < <(process_identity "$pid"); then
|
|
361
|
-
if [[ "$start" == "$expected_start" && "$state" == Z* ]]; then
|
|
362
|
-
wait "$pid" >/dev/null 2>&1 || true
|
|
363
|
-
fi
|
|
364
|
-
fi
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
terminate_validated_group() {
|
|
368
|
-
local pid=$1
|
|
369
|
-
local pgid=$2
|
|
370
|
-
local sid=$3
|
|
371
|
-
local expected_start=$4
|
|
372
|
-
local start current_pgid current_sid state attempt
|
|
373
|
-
if ! read -r start current_pgid current_sid state < <(process_identity "$pid"); then
|
|
374
|
-
return 2
|
|
375
|
-
fi
|
|
376
|
-
if [[ "$start" != "$expected_start" || "$current_pgid" != "$pgid" || "$current_sid" != "$sid" ]]; then
|
|
377
|
-
return 2
|
|
378
|
-
fi
|
|
379
|
-
|
|
380
|
-
kill -STOP -- "-$pgid" >/dev/null 2>&1 || true
|
|
381
|
-
kill -TERM -- "-$pgid" >/dev/null 2>&1 || true
|
|
382
|
-
kill -CONT -- "-$pgid" >/dev/null 2>&1 || true
|
|
383
|
-
for attempt in $(seq 1 20); do
|
|
384
|
-
reap_tracked_root_if_terminal "$pid" "$expected_start"
|
|
385
|
-
validated_group_is_alive "$pgid" || return 0
|
|
386
|
-
sleep 0.05
|
|
387
|
-
done
|
|
388
|
-
kill -KILL -- "-$pgid" >/dev/null 2>&1 || true
|
|
389
|
-
kill -CONT -- "-$pgid" >/dev/null 2>&1 || true
|
|
390
|
-
for attempt in $(seq 1 20); do
|
|
391
|
-
reap_tracked_root_if_terminal "$pid" "$expected_start"
|
|
392
|
-
validated_group_is_alive "$pgid" || return 0
|
|
393
|
-
sleep 0.05
|
|
394
|
-
done
|
|
395
|
-
kill -STOP -- "-$pgid" >/dev/null 2>&1 || true
|
|
396
|
-
return 1
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
terminate_output_job() {
|
|
400
|
-
local index=$1
|
|
401
|
-
local pid="${OUTPUT_JOB_PIDS[$index]}"
|
|
402
|
-
local pgid="${OUTPUT_JOB_PGIDS[$index]}"
|
|
403
|
-
local sid="${OUTPUT_JOB_SIDS[$index]}"
|
|
404
|
-
local expected_start="${OUTPUT_JOB_STARTS[$index]}"
|
|
405
|
-
local current_start current_pgid current_sid targets remaining target attempt
|
|
406
|
-
local tree_root=$pid
|
|
407
|
-
[[ -z "$pid" ]] && return 0
|
|
408
|
-
|
|
409
|
-
if kill -0 "$pid" 2>/dev/null; then
|
|
410
|
-
if ! read -r current_start current_pgid current_sid _ < <(process_identity "$pid"); then
|
|
411
|
-
OUTPUT_PROCESS_STATE_UNKNOWN=1
|
|
412
|
-
return 2
|
|
413
|
-
fi
|
|
414
|
-
if [[ -z "$expected_start" || "$current_start" != "$expected_start" || "$current_pgid" != "$pgid" || "$current_sid" != "$sid" ]]; then
|
|
415
|
-
echo "MIAODA_PREVIEW_STATE_UNKNOWN: output job identity changed" >&2
|
|
416
|
-
OUTPUT_PROCESS_STATE_UNKNOWN=1
|
|
417
|
-
return 2
|
|
418
|
-
fi
|
|
419
|
-
else
|
|
420
|
-
tree_root=""
|
|
421
|
-
fi
|
|
422
|
-
|
|
423
|
-
# Freeze the isolated session/tree before signaling so it cannot race cleanup
|
|
424
|
-
# by creating another writer after the snapshot.
|
|
425
|
-
for attempt in 1 2 3; do
|
|
426
|
-
if ! targets="$(collect_output_tree_pids "$tree_root" "$sid")"; then
|
|
427
|
-
OUTPUT_PROCESS_STATE_UNKNOWN=1
|
|
428
|
-
terminate_validated_group "$pid" "$pgid" "$sid" "$expected_start" || true
|
|
429
|
-
return 2
|
|
430
|
-
fi
|
|
431
|
-
for target in $targets; do
|
|
432
|
-
kill -STOP "$target" >/dev/null 2>&1 || true
|
|
433
|
-
done
|
|
434
|
-
done
|
|
435
|
-
|
|
436
|
-
if ! targets="$(collect_output_tree_pids "$tree_root" "$sid")"; then
|
|
437
|
-
OUTPUT_PROCESS_STATE_UNKNOWN=1
|
|
438
|
-
terminate_validated_group "$pid" "$pgid" "$sid" "$expected_start" || true
|
|
439
|
-
return 2
|
|
440
|
-
fi
|
|
441
|
-
for target in $targets; do
|
|
442
|
-
kill -TERM "$target" >/dev/null 2>&1 || true
|
|
443
|
-
done
|
|
444
|
-
for target in $targets; do
|
|
445
|
-
kill -CONT "$target" >/dev/null 2>&1 || true
|
|
446
|
-
done
|
|
447
|
-
|
|
448
|
-
for attempt in $(seq 1 20); do
|
|
449
|
-
if ! remaining="$(collect_output_tree_pids "$tree_root" "$sid")"; then
|
|
450
|
-
OUTPUT_PROCESS_STATE_UNKNOWN=1
|
|
451
|
-
terminate_validated_group "$pid" "$pgid" "$sid" "$expected_start" || true
|
|
452
|
-
return 2
|
|
453
|
-
fi
|
|
454
|
-
[[ -z "${remaining//[[:space:]]/}" ]] && break
|
|
455
|
-
sleep 0.05
|
|
456
|
-
done
|
|
457
|
-
if ! remaining="$(collect_output_tree_pids "$tree_root" "$sid")"; then
|
|
458
|
-
OUTPUT_PROCESS_STATE_UNKNOWN=1
|
|
459
|
-
terminate_validated_group "$pid" "$pgid" "$sid" "$expected_start" || true
|
|
460
|
-
return 2
|
|
461
|
-
fi
|
|
462
|
-
if [[ -n "${remaining//[[:space:]]/}" ]]; then
|
|
463
|
-
for target in $remaining; do
|
|
464
|
-
kill -KILL "$target" >/dev/null 2>&1 || true
|
|
465
|
-
kill -CONT "$target" >/dev/null 2>&1 || true
|
|
466
|
-
done
|
|
467
|
-
for attempt in $(seq 1 20); do
|
|
468
|
-
if ! remaining="$(collect_output_tree_pids "$tree_root" "$sid")"; then
|
|
469
|
-
OUTPUT_PROCESS_STATE_UNKNOWN=1
|
|
470
|
-
terminate_validated_group "$pid" "$pgid" "$sid" "$expected_start" || true
|
|
471
|
-
return 2
|
|
472
|
-
fi
|
|
473
|
-
[[ -z "${remaining//[[:space:]]/}" ]] && break
|
|
474
|
-
sleep 0.05
|
|
475
|
-
done
|
|
476
|
-
fi
|
|
477
|
-
|
|
478
|
-
if ! remaining="$(collect_output_tree_pids "$tree_root" "$sid")"; then
|
|
479
|
-
OUTPUT_PROCESS_STATE_UNKNOWN=1
|
|
480
|
-
terminate_validated_group "$pid" "$pgid" "$sid" "$expected_start" || true
|
|
481
|
-
return 2
|
|
482
|
-
fi
|
|
483
|
-
if [[ -n "${remaining//[[:space:]]/}" ]]; then
|
|
484
|
-
for target in $remaining; do
|
|
485
|
-
kill -STOP "$target" >/dev/null 2>&1 || true
|
|
486
|
-
done
|
|
487
|
-
echo "MIAODA_PREVIEW_STATE_UNKNOWN: output process tree did not terminate" >&2
|
|
488
|
-
OUTPUT_PROCESS_STATE_UNKNOWN=1
|
|
489
|
-
return 1
|
|
490
|
-
fi
|
|
491
|
-
wait "$pid" >/dev/null 2>&1 || true
|
|
492
|
-
forget_output_job "$pid"
|
|
493
|
-
return 0
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
terminate_output_jobs() {
|
|
497
|
-
local index failed=0
|
|
498
|
-
for index in "${!OUTPUT_JOB_PIDS[@]}"; do
|
|
499
|
-
[[ -z "${OUTPUT_JOB_PIDS[$index]}" ]] && continue
|
|
500
|
-
terminate_output_job "$index" || failed=1
|
|
501
|
-
done
|
|
502
|
-
return "$failed"
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
stop_lease_heartbeat() {
|
|
506
|
-
local pid=$OUTPUT_LEASE_HEARTBEAT_PID
|
|
507
|
-
local expected_start=$OUTPUT_LEASE_HEARTBEAT_START
|
|
508
|
-
local current_start
|
|
509
|
-
[[ -z "$pid" ]] && return 0
|
|
510
|
-
if kill -0 "$pid" 2>/dev/null; then
|
|
511
|
-
current_start="$(process_start_identity "$pid")" || return 1
|
|
512
|
-
[[ -n "$expected_start" && "$current_start" == "$expected_start" ]] || return 1
|
|
513
|
-
kill -TERM "$pid" >/dev/null 2>&1 || true
|
|
514
|
-
if ! wait_for_tracked_pid_terminal "$pid" "$expected_start" 20; then
|
|
515
|
-
kill -KILL "$pid" >/dev/null 2>&1 || true
|
|
516
|
-
wait_for_tracked_pid_terminal "$pid" "$expected_start" 20 || return 1
|
|
517
|
-
fi
|
|
518
|
-
else
|
|
519
|
-
wait "$pid" >/dev/null 2>&1 || true
|
|
520
|
-
fi
|
|
521
|
-
OUTPUT_LEASE_HEARTBEAT_PID=""
|
|
522
|
-
OUTPUT_LEASE_HEARTBEAT_START=""
|
|
523
|
-
return 0
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
quarantine_output_lease() {
|
|
527
|
-
local attempt
|
|
528
|
-
[[ "$OUTPUT_LEASE_ACQUIRED" == "1" ]] || return 0
|
|
529
|
-
for attempt in 1 2 3; do
|
|
530
|
-
if npx fullstack-cli output-lease poison \
|
|
531
|
-
--root "$ROOT_DIR" --out-dir "$DIST_DIR/client" >/dev/null 2>&1; then
|
|
532
|
-
OUTPUT_LEASE_ACQUIRED=0
|
|
533
|
-
return 0
|
|
534
|
-
fi
|
|
535
|
-
done
|
|
536
|
-
echo "MIAODA_PREVIEW_STATE_UNKNOWN: failed to quarantine build output lease" >&2
|
|
537
|
-
return 1
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
release_output_lease() {
|
|
541
|
-
local status=$?
|
|
542
|
-
local output_jobs_stopped=1
|
|
543
|
-
trap - EXIT HUP INT TERM
|
|
544
|
-
set +e
|
|
545
|
-
|
|
546
|
-
# Output producers and their isolated sessions must be terminal before the
|
|
547
|
-
# heartbeat can stop or ownership can be released.
|
|
548
|
-
terminate_output_jobs || output_jobs_stopped=0
|
|
549
|
-
if [[ "$output_jobs_stopped" != "1" || "$OUTPUT_PROCESS_STATE_UNKNOWN" == "1" ]]; then
|
|
550
|
-
echo "MIAODA_PREVIEW_STATE_UNKNOWN: quarantining lease because output termination is unproven" >&2
|
|
551
|
-
quarantine_output_lease || true
|
|
552
|
-
stop_lease_heartbeat || true
|
|
553
|
-
rm -f "$OUTPUT_LEASE_HEARTBEAT_LOG"
|
|
554
|
-
unset MIAODA_PREVIEW_OUTPUT_LEASE_V1
|
|
555
|
-
[[ $status -eq 0 ]] && status=1
|
|
556
|
-
exit "$status"
|
|
557
|
-
fi
|
|
558
|
-
|
|
559
|
-
if ! stop_lease_heartbeat; then
|
|
560
|
-
echo "MIAODA_PREVIEW_STATE_UNKNOWN: quarantining lease because heartbeat termination is unproven" >&2
|
|
561
|
-
quarantine_output_lease || true
|
|
562
|
-
rm -f "$OUTPUT_LEASE_HEARTBEAT_LOG"
|
|
563
|
-
unset MIAODA_PREVIEW_OUTPUT_LEASE_V1
|
|
564
|
-
[[ $status -eq 0 ]] && status=1
|
|
565
|
-
exit "$status"
|
|
566
|
-
fi
|
|
567
|
-
if [[ "$OUTPUT_LEASE_ACQUIRED" == "1" ]]; then
|
|
568
|
-
if ! npx fullstack-cli output-lease release \
|
|
569
|
-
--root "$ROOT_DIR" --out-dir "$DIST_DIR/client" >/dev/null; then
|
|
570
|
-
echo "MIAODA_PREVIEW_STATE_UNKNOWN: failed to release build output lease" >&2
|
|
571
|
-
[[ $status -eq 0 ]] && status=1
|
|
572
|
-
fi
|
|
573
|
-
fi
|
|
574
|
-
rm -f "$OUTPUT_LEASE_HEARTBEAT_LOG"
|
|
575
|
-
unset MIAODA_PREVIEW_OUTPUT_LEASE_V1
|
|
576
|
-
exit "$status"
|
|
577
|
-
}
|
|
578
|
-
handle_output_signal() {
|
|
579
|
-
if [[ "$OUTPUT_JOB_LAUNCHING" == "1" ]]; then
|
|
580
|
-
OUTPUT_PENDING_SIGNAL_STATUS=$1
|
|
581
|
-
return
|
|
582
|
-
fi
|
|
583
|
-
exit "$1"
|
|
584
|
-
}
|
|
585
|
-
trap release_output_lease EXIT
|
|
586
|
-
trap 'handle_output_signal 129' HUP
|
|
587
|
-
trap 'handle_output_signal 130' INT
|
|
588
|
-
trap 'handle_output_signal 143' TERM
|
|
589
|
-
|
|
590
|
-
MIAODA_PREVIEW_OUTPUT_LEASE_V1="$(
|
|
591
|
-
npx fullstack-cli output-lease acquire \
|
|
592
|
-
--root "$ROOT_DIR" --out-dir "$DIST_DIR/client" --owner-pid "$$"
|
|
593
|
-
)"
|
|
594
|
-
export MIAODA_PREVIEW_OUTPUT_LEASE_V1
|
|
595
|
-
OUTPUT_LEASE_ACQUIRED=1
|
|
596
|
-
npx fullstack-cli output-lease heartbeat \
|
|
597
|
-
--root "$ROOT_DIR" --out-dir "$DIST_DIR/client" \
|
|
598
|
-
>"$OUTPUT_LEASE_HEARTBEAT_LOG" 2>&1 &
|
|
599
|
-
OUTPUT_LEASE_HEARTBEAT_PID=$!
|
|
600
|
-
for attempt in $(seq 1 50); do
|
|
601
|
-
OUTPUT_LEASE_HEARTBEAT_START="$(process_start_identity "$OUTPUT_LEASE_HEARTBEAT_PID")" || true
|
|
602
|
-
[[ -n "$OUTPUT_LEASE_HEARTBEAT_START" ]] && break
|
|
603
|
-
kill -0 "$OUTPUT_LEASE_HEARTBEAT_PID" 2>/dev/null || break
|
|
604
|
-
sleep 0.01
|
|
605
|
-
done
|
|
606
|
-
if [[ -z "$OUTPUT_LEASE_HEARTBEAT_START" ]]; then
|
|
607
|
-
OUTPUT_PROCESS_STATE_UNKNOWN=1
|
|
608
|
-
echo "MIAODA_PREVIEW_STATE_UNKNOWN: cannot identify lease heartbeat" >&2
|
|
609
|
-
exit 1
|
|
610
|
-
fi
|
|
611
|
-
# Synchronous verification closes the acquire-to-first-write gap and proves the
|
|
612
|
-
# token belongs to this wrapper process tree.
|
|
613
|
-
npx fullstack-cli output-lease verify \
|
|
614
|
-
--root "$ROOT_DIR" --out-dir "$DIST_DIR/client"
|
|
615
|
-
if ! command -v setsid >/dev/null 2>&1; then
|
|
616
|
-
echo "MIAODA_PREVIEW_STATE_UNKNOWN: setsid is required for safe output cleanup" >&2
|
|
617
|
-
exit 1
|
|
618
|
-
fi
|
|
619
|
-
|
|
620
35
|
# ==================== 步骤 2 ====================
|
|
621
36
|
echo "🗑️ [2/6] 清理 dist 目录"
|
|
622
37
|
STEP_START=$(node -e "console.log(Date.now())")
|
|
@@ -631,20 +46,18 @@ STEP_START=$(node -e "console.log(Date.now())")
|
|
|
631
46
|
# 在 client/server 构建之前生成到 dist/,供 DefinePlugin 注入前端 bundle
|
|
632
47
|
# 注意:nest-cli.json 中 deleteOutDir 必须为 false(模板默认值),否则 nest build 会清掉 dist/
|
|
633
48
|
echo " ├─ 生成 API 路由定义..."
|
|
634
|
-
|
|
635
|
-
|
|
49
|
+
npx generate-api-routes --server-dir ./server --out-dir ./dist > /tmp/gen-api-routes.log 2>&1 &
|
|
50
|
+
API_ROUTES_PID=$!
|
|
636
51
|
|
|
637
52
|
echo " ├─ 生成页面路由定义..."
|
|
638
|
-
|
|
639
|
-
|
|
53
|
+
npx generate-page-routes --app-path ./client/src/app.tsx --out-dir ./dist > /tmp/gen-page-routes.log 2>&1 &
|
|
54
|
+
PAGE_ROUTES_PID=$!
|
|
640
55
|
|
|
641
56
|
API_ROUTES_EXIT=0
|
|
642
57
|
PAGE_ROUTES_EXIT=0
|
|
643
58
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
wait_output_job "$PAGE_ROUTES_PID" || PAGE_ROUTES_EXIT=$?
|
|
647
|
-
[[ "$OUTPUT_PROCESS_STATE_UNKNOWN" == "1" ]] && exit 125
|
|
59
|
+
wait $API_ROUTES_PID || API_ROUTES_EXIT=$?
|
|
60
|
+
wait $PAGE_ROUTES_PID || PAGE_ROUTES_EXIT=$?
|
|
648
61
|
|
|
649
62
|
if [ $API_ROUTES_EXIT -ne 0 ]; then
|
|
650
63
|
echo " ⚠️ API 路由生成失败(不影响构建)"
|
|
@@ -676,16 +89,13 @@ if [[ "${only_frontend_change:-false}" == "true" ]]; then
|
|
|
676
89
|
echo "🔨 [4/6] 仅构建 client (only_frontend_change=true)"
|
|
677
90
|
|
|
678
91
|
echo " ├─ 启动 client 构建..."
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
CLIENT_EXIT=0
|
|
682
|
-
wait_output_job "$CLIENT_PID" || CLIENT_EXIT=$?
|
|
683
|
-
[[ "$OUTPUT_PROCESS_STATE_UNKNOWN" == "1" ]] && exit 125
|
|
92
|
+
NODE_OPTIONS="$BUILD_NODE_OPTIONS" npm run build:client > /tmp/build-client.log 2>&1
|
|
93
|
+
CLIENT_EXIT=$?
|
|
684
94
|
|
|
685
95
|
if [ $CLIENT_EXIT -ne 0 ]; then
|
|
686
96
|
echo " ❌ Client 构建失败"
|
|
687
97
|
cat /tmp/build-client.log
|
|
688
|
-
exit
|
|
98
|
+
exit 1
|
|
689
99
|
fi
|
|
690
100
|
|
|
691
101
|
echo " ✅ Client 构建完成"
|
|
@@ -694,33 +104,31 @@ else
|
|
|
694
104
|
|
|
695
105
|
# 并行构建
|
|
696
106
|
echo " ├─ 启动 server 构建..."
|
|
697
|
-
|
|
698
|
-
|
|
107
|
+
NODE_OPTIONS="$BUILD_NODE_OPTIONS" npm run build:server > /tmp/build-server.log 2>&1 &
|
|
108
|
+
SERVER_PID=$!
|
|
699
109
|
|
|
700
110
|
echo " ├─ 启动 client 构建..."
|
|
701
|
-
|
|
702
|
-
|
|
111
|
+
NODE_OPTIONS="$BUILD_NODE_OPTIONS" npm run build:client > /tmp/build-client.log 2>&1 &
|
|
112
|
+
CLIENT_PID=$!
|
|
703
113
|
|
|
704
114
|
# 等待两个构建完成
|
|
705
115
|
SERVER_EXIT=0
|
|
706
116
|
CLIENT_EXIT=0
|
|
707
117
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
wait_output_job "$CLIENT_PID" || CLIENT_EXIT=$?
|
|
711
|
-
[[ "$OUTPUT_PROCESS_STATE_UNKNOWN" == "1" ]] && exit 125
|
|
118
|
+
wait $SERVER_PID || SERVER_EXIT=$?
|
|
119
|
+
wait $CLIENT_PID || CLIENT_EXIT=$?
|
|
712
120
|
|
|
713
121
|
# 检查构建结果
|
|
714
122
|
if [ $SERVER_EXIT -ne 0 ]; then
|
|
715
123
|
echo " ❌ Server 构建失败"
|
|
716
124
|
cat /tmp/build-server.log
|
|
717
|
-
exit
|
|
125
|
+
exit 1
|
|
718
126
|
fi
|
|
719
127
|
|
|
720
128
|
if [ $CLIENT_EXIT -ne 0 ]; then
|
|
721
129
|
echo " ❌ Client 构建失败"
|
|
722
130
|
cat /tmp/build-client.log
|
|
723
|
-
exit
|
|
131
|
+
exit 1
|
|
724
132
|
fi
|
|
725
133
|
|
|
726
134
|
echo " ✅ Server 构建完成"
|