@devrouter/cli 0.0.52 → 0.0.54

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.
@@ -10,6 +10,13 @@ Usage:
10
10
 
11
11
  Options:
12
12
  --fingerprint <value> Runtime identity. Defaults to command, workspace, adapter, and safe allowlisted environment identity.
13
+ An explicit fingerprint is caller-owned, including preparation identity.
14
+ --prepare-command <shell command>
15
+ Ensure only: run a nonempty command with bash before a changed/new runtime.
16
+ Included byte-for-byte in the default fingerprint; skipped on runtime reuse.
17
+ Must run synchronously in the foreground: no daemonizing, detached sessions
18
+ or process groups, or other supervision escapes. Cancellation terminates
19
+ its original group and reaps the direct child; container init reaps orphans.
13
20
  --log <path> Log file. Defaults to /tmp/devrouter-process-<name>.log.
14
21
 
15
22
  Environment:
@@ -42,6 +49,7 @@ name=""
42
49
  process_match=""
43
50
  fingerprint=""
44
51
  log_file=""
52
+ prepare_command=""
45
53
 
46
54
  while [ "$#" -gt 0 ]; do
47
55
  case "$1" in
@@ -68,6 +76,13 @@ while [ "$#" -gt 0 ]; do
68
76
  log_file="$2"
69
77
  shift 2
70
78
  ;;
79
+ --prepare-command)
80
+ [ "$action" = "ensure" ] || die "--prepare-command is only valid for ensure."
81
+ require_value "$@"
82
+ [[ "$2" =~ [^[:space:]] ]] || die "--prepare-command requires a nonempty command."
83
+ prepare_command="$2"
84
+ shift 2
85
+ ;;
71
86
  --help|-h)
72
87
  usage
73
88
  exit 0
@@ -151,6 +166,9 @@ if [ -z "$fingerprint" ]; then
151
166
  for argument in "$@"; do
152
167
  printf 'argv\0%s\0' "$argument"
153
168
  done
169
+ if [ -n "$prepare_command" ]; then
170
+ printf 'prepare-command\0%s\0' "$prepare_command"
171
+ fi
154
172
  for env_name in "${fingerprint_env_names[@]}"; do
155
173
  printf 'environment-name\0%s\0' "$env_name"
156
174
  emit_environment_field environment-value "$env_name"
@@ -385,18 +403,87 @@ fi
385
403
  unknown_process_exists &&
386
404
  die "Found an unowned '$name' process; refusing to start a duplicate or kill it."
387
405
 
406
+ preparation_pid=""
407
+ pending_runtime_pid=""
408
+ interrupted=0
409
+
410
+ # The direct child can still be between fork and setsid when cancellation arrives.
411
+ # Signal both that child and its eventual group, and never wait on a live child.
412
+ stop_preparation_child() {
413
+ local child="$1"
414
+ local signal attempt limit
415
+
416
+ for signal in TERM KILL; do
417
+ limit="$term_timeout"
418
+ [ "$signal" != KILL ] || limit="$kill_timeout"
419
+ kill -"$signal" -- "-$child" "$child" 2>/dev/null || true
420
+ for ((attempt = 0; attempt <= limit; attempt += 1)); do
421
+ if ! process_group_alive "$child" && ! process_alive "$child"; then
422
+ wait "$child" 2>/dev/null || true
423
+ return 0
424
+ fi
425
+ [ "$attempt" -eq "$limit" ] || sleep 1
426
+ done
427
+ done
428
+ echo "[devrouter-process] Could not terminate preparation/startup group $child." >&2
429
+ return 1
430
+ }
431
+
432
+ cleanup_preparation() {
433
+ local result="$?"
434
+ # Repeated cancellation must not interrupt group cleanup or release the lock early.
435
+ trap '' HUP INT TERM
436
+ if [ -n "$preparation_pid" ]; then
437
+ stop_preparation_child "$preparation_pid" || result=1
438
+ fi
439
+ if [ -n "$pending_runtime_pid" ]; then
440
+ stop_preparation_child "$pending_runtime_pid" || result=1
441
+ rm -f "$state_file" "$state_file.tmp.$BASHPID"
442
+ fi
443
+ trap - EXIT
444
+ exit "$result"
445
+ }
446
+
447
+ if [ -n "$prepare_command" ]; then
448
+ trap 'interrupted=129' HUP
449
+ trap 'interrupted=130' INT
450
+ trap 'interrupted=143' TERM
451
+ trap cleanup_preparation EXIT
452
+ echo "[devrouter-process] Preparing '$name'..."
453
+ env DEVROUTER_PROCESS_NAME="$name" \
454
+ DEVROUTER_PROCESS_FINGERPRINT="$fingerprint" \
455
+ setsid bash -c "$prepare_command" 9>&- </dev/null &
456
+ preparation_pid=$!
457
+ # A trapped signal interrupts Bash's wait. Record it even if it arrives before
458
+ # assignment of $!, then enter cleanup with the exact child still recorded.
459
+ [ "$interrupted" -eq 0 ] || exit "$interrupted"
460
+ preparation_status=0
461
+ wait "$preparation_pid" || preparation_status=$?
462
+ [ "$interrupted" -eq 0 ] || exit "$interrupted"
463
+ [ "$preparation_status" -eq 0 ] || die "Preparation for '$name' failed (exit $preparation_status)."
464
+ if process_group_alive "$preparation_pid"; then
465
+ die "Preparation for '$name' left running children; a synchronous foreground command is required."
466
+ fi
467
+ preparation_pid=""
468
+ fi
469
+
470
+ [ "$interrupted" -eq 0 ] || exit "$interrupted"
388
471
  echo "[devrouter-process] Starting '$name' (logs: $log_file)..."
389
472
  env DEVROUTER_PROCESS_NAME="$name" \
390
473
  DEVROUTER_PROCESS_FINGERPRINT="$fingerprint" \
391
474
  setsid "$@" 9>&- >"$log_file" 2>&1 </dev/null &
392
475
  pid=$!
393
476
  pgid="$pid"
477
+ [ -z "$prepare_command" ] || pending_runtime_pid="$pid"
394
478
 
395
479
  for attempt in {1..10}; do
480
+ [ "$interrupted" -eq 0 ] || exit "$interrupted"
396
481
  if process_owned "$pid" "$pgid" "$fingerprint"; then
397
482
  printf '%s %s %s\n' "$pid" "$pgid" "$fingerprint" >"$state_file.tmp.$BASHPID"
398
483
  mv "$state_file.tmp.$BASHPID" "$state_file"
484
+ [ "$interrupted" -eq 0 ] || exit "$interrupted"
399
485
  echo "[devrouter-process] '$name' started (PID $pid)."
486
+ pending_runtime_pid=""
400
487
  exit 0
401
488
  fi
402
489
  if ! process_alive "$pid"; then