@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.
@@ -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.64",
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.72",
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
- start_output_job API_ROUTES_PID /tmp/gen-api-routes.log \
635
- npx generate-api-routes --server-dir ./server --out-dir ./dist
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
- start_output_job PAGE_ROUTES_PID /tmp/gen-page-routes.log \
639
- npx generate-page-routes --app-path ./client/src/app.tsx --out-dir ./dist
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
- wait_output_job "$API_ROUTES_PID" || API_ROUTES_EXIT=$?
645
- [[ "$OUTPUT_PROCESS_STATE_UNKNOWN" == "1" ]] && exit 125
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
- start_output_job CLIENT_PID /tmp/build-client.log \
680
- env NODE_OPTIONS="$BUILD_NODE_OPTIONS" npm run build:client
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 "$CLIENT_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
- start_output_job SERVER_PID /tmp/build-server.log \
698
- env NODE_OPTIONS="$BUILD_NODE_OPTIONS" npm run build:server
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
- start_output_job CLIENT_PID /tmp/build-client.log \
702
- env NODE_OPTIONS="$BUILD_NODE_OPTIONS" npm run build:client
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
- wait_output_job "$SERVER_PID" || SERVER_EXIT=$?
709
- [[ "$OUTPUT_PROCESS_STATE_UNKNOWN" == "1" ]] && exit 125
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 "$SERVER_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 "$CLIENT_EXIT"
131
+ exit 1
724
132
  fi
725
133
 
726
134
  echo " ✅ Server 构建完成"