@devrouter/cli 0.0.51 → 0.0.53

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"
@@ -187,9 +205,9 @@ process_owned() {
187
205
  [ "$actual_pgid" = "$pgid" ] || return 1
188
206
  [ "$pgid" = "$pid" ] || return 1
189
207
  tr '\0' '\n' <"/proc/$pid/environ" 2>/dev/null |
190
- grep -Fqx "DEVROUTER_PROCESS_NAME=$name" || return 1
208
+ grep -Fx "DEVROUTER_PROCESS_NAME=$name" >/dev/null || return 1
191
209
  tr '\0' '\n' <"/proc/$pid/environ" 2>/dev/null |
192
- grep -Fqx "DEVROUTER_PROCESS_FINGERPRINT=$expected_fingerprint"
210
+ grep -Fx "DEVROUTER_PROCESS_FINGERPRINT=$expected_fingerprint" >/dev/null
193
211
  }
194
212
 
195
213
  process_group_alive() {
@@ -217,7 +235,7 @@ is_helper_process() {
217
235
  local candidate="$1"
218
236
 
219
237
  [ -r "/proc/$candidate/cmdline" ] || return 1
220
- tr '\0' '\n' <"/proc/$candidate/cmdline" 2>/dev/null | grep -Fqx "$0"
238
+ tr '\0' '\n' <"/proc/$candidate/cmdline" 2>/dev/null | grep -Fx "$0" >/dev/null
221
239
  }
222
240
 
223
241
  marked_process_exists() {
@@ -230,7 +248,7 @@ marked_process_exists() {
230
248
  pid="${pid%/environ}"
231
249
  process_alive "$pid" || continue
232
250
  is_self_or_ancestor "$pid" && continue
233
- if tr '\0' '\n' <"$environ" 2>/dev/null | grep -Fqx "DEVROUTER_PROCESS_NAME=$name"; then
251
+ if tr '\0' '\n' <"$environ" 2>/dev/null | grep -Fx "DEVROUTER_PROCESS_NAME=$name" >/dev/null; then
234
252
  return 0
235
253
  fi
236
254
  done
@@ -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