command-stream 0.8.2 → 0.9.0
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/js/examples/01-basic-streaming.mjs +14 -0
- package/js/examples/02-async-iterator.mjs +9 -0
- package/js/examples/03-file-and-console.mjs +16 -0
- package/js/examples/04-claude-jq-pipe.mjs +16 -0
- package/js/examples/CI-DEBUG-README.md +138 -0
- package/js/examples/README-examples.mjs +111 -0
- package/js/examples/README.md +345 -0
- package/js/examples/STREAMING_INTERFACES_SUMMARY.md +116 -0
- package/js/examples/add-test-timeouts.js +107 -0
- package/js/examples/ansi-default-preserved.mjs +11 -0
- package/js/examples/ansi-global-config.mjs +12 -0
- package/js/examples/ansi-reset-default.mjs +12 -0
- package/js/examples/ansi-strip-utils.mjs +12 -0
- package/js/examples/baseline-child-process.mjs +23 -0
- package/js/examples/baseline-claude-test.mjs +47 -0
- package/js/examples/baseline-working.mjs +34 -0
- package/js/examples/capture-mirror-comparison.mjs +23 -0
- package/js/examples/capture-mirror-default.mjs +11 -0
- package/js/examples/capture-mirror-performance.mjs +16 -0
- package/js/examples/capture-mirror-show-only.mjs +16 -0
- package/js/examples/capture-mirror-silent-processing.mjs +16 -0
- package/js/examples/ci-debug-baseline-vs-library.mjs +265 -0
- package/js/examples/ci-debug-es-module-loading.mjs +184 -0
- package/js/examples/ci-debug-signal-handling.mjs +225 -0
- package/js/examples/ci-debug-stdout-buffering.mjs +94 -0
- package/js/examples/ci-debug-test-timeouts.mjs +259 -0
- package/js/examples/claude-exact-file-output.mjs +20 -0
- package/js/examples/claude-exact-jq.mjs +13 -0
- package/js/examples/claude-exact-streaming.mjs +13 -0
- package/js/examples/claude-jq-pipeline.mjs +13 -0
- package/js/examples/claude-json-stream.mjs +39 -0
- package/js/examples/claude-streaming-basic.mjs +99 -0
- package/js/examples/claude-streaming-demo.mjs +126 -0
- package/js/examples/claude-streaming-final.mjs +20 -0
- package/js/examples/cleanup-verification-test.mjs +115 -0
- package/js/examples/colors-buffer-processing.mjs +14 -0
- package/js/examples/colors-default-preserved.mjs +15 -0
- package/js/examples/colors-per-command-config.mjs +15 -0
- package/js/examples/colors-strip-ansi.mjs +13 -0
- package/js/examples/commandstream-jq.mjs +29 -0
- package/js/examples/commandstream-working.mjs +23 -0
- package/js/examples/comprehensive-streams-demo.mjs +121 -0
- package/js/examples/ctrl-c-concurrent-processes.mjs +20 -0
- package/js/examples/ctrl-c-long-running-command.mjs +20 -0
- package/js/examples/ctrl-c-real-system-command.mjs +17 -0
- package/js/examples/ctrl-c-sleep-command.mjs +17 -0
- package/js/examples/ctrl-c-stdin-forwarding.mjs +20 -0
- package/js/examples/ctrl-c-virtual-command.mjs +17 -0
- package/js/examples/debug-already-started.mjs +20 -0
- package/js/examples/debug-ansi-processing.mjs +42 -0
- package/js/examples/debug-basic-streaming.mjs +44 -0
- package/js/examples/debug-buildshellcommand.mjs +82 -0
- package/js/examples/debug-child-process.mjs +43 -0
- package/js/examples/debug-child-state.mjs +55 -0
- package/js/examples/debug-chunking.mjs +38 -0
- package/js/examples/debug-command-parsing.mjs +61 -0
- package/js/examples/debug-complete-consolidation.mjs +97 -0
- package/js/examples/debug-echo-args.mjs +22 -0
- package/js/examples/debug-emit-timing.mjs +28 -0
- package/js/examples/debug-end-event.mjs +56 -0
- package/js/examples/debug-errexit.mjs +16 -0
- package/js/examples/debug-event-emission.mjs +40 -0
- package/js/examples/debug-event-timing.mjs +67 -0
- package/js/examples/debug-event-vs-result.mjs +30 -0
- package/js/examples/debug-exact-command.mjs +28 -0
- package/js/examples/debug-exact-test-scenario.mjs +44 -0
- package/js/examples/debug-execution-path.mjs +27 -0
- package/js/examples/debug-exit-command.mjs +38 -0
- package/js/examples/debug-exit-virtual.mjs +54 -0
- package/js/examples/debug-finish-consolidation.mjs +44 -0
- package/js/examples/debug-force-cleanup.mjs +47 -0
- package/js/examples/debug-getter-basic.mjs +13 -0
- package/js/examples/debug-getter-direct.mjs +23 -0
- package/js/examples/debug-getter-internals.mjs +61 -0
- package/js/examples/debug-handler-detection.mjs +65 -0
- package/js/examples/debug-idempotent-finish.mjs +54 -0
- package/js/examples/debug-idempotent-kill.mjs +58 -0
- package/js/examples/debug-interpolation-individual.mjs +88 -0
- package/js/examples/debug-interpolation-issue.mjs +101 -0
- package/js/examples/debug-jq-streaming.mjs +57 -0
- package/js/examples/debug-jq-tty-colors.mjs +168 -0
- package/js/examples/debug-kill-cleanup.mjs +56 -0
- package/js/examples/debug-kill-method.mjs +33 -0
- package/js/examples/debug-listener-interference.mjs +38 -0
- package/js/examples/debug-listener-lifecycle.mjs +50 -0
- package/js/examples/debug-listener-timing.mjs +62 -0
- package/js/examples/debug-listeners-property.mjs +34 -0
- package/js/examples/debug-map-methods.mjs +39 -0
- package/js/examples/debug-not-awaited-cleanup.mjs +106 -0
- package/js/examples/debug-off-method.mjs +28 -0
- package/js/examples/debug-option-merging.mjs +17 -0
- package/js/examples/debug-options.mjs +36 -0
- package/js/examples/debug-output.mjs +25 -0
- package/js/examples/debug-pattern-matching.mjs +69 -0
- package/js/examples/debug-pipeline-cat.mjs +28 -0
- package/js/examples/debug-pipeline-cleanup.mjs +71 -0
- package/js/examples/debug-pipeline-error-detailed.mjs +78 -0
- package/js/examples/debug-pipeline-error.mjs +65 -0
- package/js/examples/debug-pipeline-issue.mjs +26 -0
- package/js/examples/debug-pipeline-method.mjs +22 -0
- package/js/examples/debug-pipeline-stream.mjs +66 -0
- package/js/examples/debug-pipeline.mjs +14 -0
- package/js/examples/debug-process-exit-trace.mjs +41 -0
- package/js/examples/debug-process-path.mjs +38 -0
- package/js/examples/debug-property-check.mjs +29 -0
- package/js/examples/debug-resource-cleanup.mjs +83 -0
- package/js/examples/debug-shell-args.mjs +103 -0
- package/js/examples/debug-sigint-child-handler.mjs +44 -0
- package/js/examples/debug-sigint-forwarding.mjs +61 -0
- package/js/examples/debug-sigint-handler-install.mjs +79 -0
- package/js/examples/debug-sigint-handler-order.mjs +85 -0
- package/js/examples/debug-sigint-listeners.mjs +48 -0
- package/js/examples/debug-sigint-start-pattern.mjs +62 -0
- package/js/examples/debug-sigint-timer.mjs +40 -0
- package/js/examples/debug-simple-command.mjs +49 -0
- package/js/examples/debug-simple-getter.mjs +30 -0
- package/js/examples/debug-simple.mjs +39 -0
- package/js/examples/debug-simplified-finished.mjs +102 -0
- package/js/examples/debug-stack-overflow.mjs +25 -0
- package/js/examples/debug-stdin-simple.mjs +28 -0
- package/js/examples/debug-stdin.mjs +28 -0
- package/js/examples/debug-stream-emitter-isolated.mjs +45 -0
- package/js/examples/debug-stream-emitter.mjs +25 -0
- package/js/examples/debug-stream-events.mjs +70 -0
- package/js/examples/debug-stream-generator.mjs +23 -0
- package/js/examples/debug-stream-getter-issue.mjs +37 -0
- package/js/examples/debug-stream-getter.mjs +19 -0
- package/js/examples/debug-stream-internals.mjs +64 -0
- package/js/examples/debug-stream-method.mjs +46 -0
- package/js/examples/debug-stream-object.mjs +58 -0
- package/js/examples/debug-stream-properties.mjs +37 -0
- package/js/examples/debug-stream-timing.mjs +28 -0
- package/js/examples/debug-streaming.mjs +24 -0
- package/js/examples/debug-test-isolation.mjs +54 -0
- package/js/examples/debug-test-state.mjs +27 -0
- package/js/examples/debug-user-sigint.mjs +19 -0
- package/js/examples/debug-virtual-disable.mjs +21 -0
- package/js/examples/debug-virtual-vs-real.mjs +68 -0
- package/js/examples/debug-with-trace.mjs +23 -0
- package/js/examples/debug_parent_stream.mjs +22 -0
- package/js/examples/emulate-claude-stream.mjs +30 -0
- package/js/examples/emulated-streaming-direct.mjs +22 -0
- package/js/examples/emulated-streaming-jq-pipe.mjs +22 -0
- package/js/examples/emulated-streaming-sh-pipe.mjs +23 -0
- package/js/examples/events-build-process.mjs +73 -0
- package/js/examples/events-concurrent-streams.mjs +51 -0
- package/js/examples/events-error-handling.mjs +59 -0
- package/js/examples/events-file-monitoring.mjs +66 -0
- package/js/examples/events-interactive-simulation.mjs +69 -0
- package/js/examples/events-log-processing.mjs +72 -0
- package/js/examples/events-network-monitoring.mjs +68 -0
- package/js/examples/events-ping-basic.mjs +37 -0
- package/js/examples/events-progress-tracking.mjs +55 -0
- package/js/examples/events-stdin-input.mjs +34 -0
- package/js/examples/example-ansi-ls.mjs +39 -0
- package/js/examples/example-top.mjs +27 -0
- package/js/examples/final-ping-stdin-proof.mjs +88 -0
- package/js/examples/final-test-shell-operators.mjs +123 -0
- package/js/examples/final-working-examples.mjs +162 -0
- package/js/examples/gh-auth-test.mjs +103 -0
- package/js/examples/gh-delete-hang-test.mjs +79 -0
- package/js/examples/gh-gist-creation-test.mjs +276 -0
- package/js/examples/gh-gist-minimal-test.mjs +89 -0
- package/js/examples/gh-hang-exact-original.mjs +83 -0
- package/js/examples/gh-hang-reproduction.mjs +151 -0
- package/js/examples/gh-hang-test-with-redirect.mjs +45 -0
- package/js/examples/gh-hang-test-without-redirect.mjs +43 -0
- package/js/examples/gh-minimal-hang-check.mjs +33 -0
- package/js/examples/gh-operations-with-cd.mjs +187 -0
- package/js/examples/gh-output-test.mjs +102 -0
- package/js/examples/gh-reproduce-hang.mjs +41 -0
- package/js/examples/git-operations-with-cd.mjs +186 -0
- package/js/examples/interactive-math-calc.mjs +45 -0
- package/js/examples/interactive-top-fixed.mjs +25 -0
- package/js/examples/interactive-top-improved.mjs +72 -0
- package/js/examples/interactive-top-pty-logging.mjs +69 -0
- package/js/examples/interactive-top-pty.mjs +27 -0
- package/js/examples/interactive-top-with-logging.mjs +56 -0
- package/js/examples/interactive-top.mjs +29 -0
- package/js/examples/jq-color-demo.mjs +53 -0
- package/js/examples/jq-colors-streaming.mjs +42 -0
- package/js/examples/manual-ctrl-c-test.mjs +50 -0
- package/js/examples/methods-multiple-options.mjs +25 -0
- package/js/examples/methods-run-basic.mjs +10 -0
- package/js/examples/methods-start-basic.mjs +10 -0
- package/js/examples/node-compat-data-events.mjs +36 -0
- package/js/examples/node-compat-readable-event.mjs +29 -0
- package/js/examples/node-compat-small-buffer.mjs +33 -0
- package/js/examples/options-capture-false.mjs +12 -0
- package/js/examples/options-combined-settings.mjs +13 -0
- package/js/examples/options-custom-input.mjs +16 -0
- package/js/examples/options-default-behavior.mjs +10 -0
- package/js/examples/options-maximum-performance.mjs +15 -0
- package/js/examples/options-mirror-false.mjs +10 -0
- package/js/examples/options-performance-mode.mjs +13 -0
- package/js/examples/options-performance-optimization.mjs +14 -0
- package/js/examples/options-run-alias-demo.mjs +15 -0
- package/js/examples/options-run-alias.mjs +10 -0
- package/js/examples/options-silent-execution.mjs +14 -0
- package/js/examples/options-streaming-capture.mjs +24 -0
- package/js/examples/options-streaming-multiple.mjs +35 -0
- package/js/examples/options-streaming-silent.mjs +21 -0
- package/js/examples/options-streaming-stdin.mjs +21 -0
- package/js/examples/ping-streaming-filtered.mjs +22 -0
- package/js/examples/ping-streaming-interruptible.mjs +47 -0
- package/js/examples/ping-streaming-silent.mjs +24 -0
- package/js/examples/ping-streaming-simple.mjs +13 -0
- package/js/examples/ping-streaming-statistics.mjs +49 -0
- package/js/examples/ping-streaming-timestamps.mjs +22 -0
- package/js/examples/ping-streaming.mjs +48 -0
- package/js/examples/prove-ping-stdin-limitation.mjs +94 -0
- package/js/examples/readme-example.mjs +39 -0
- package/js/examples/realtime-json-stream.mjs +143 -0
- package/js/examples/reliable-stdin-commands.mjs +135 -0
- package/js/examples/reproduce-issue-135-v2.mjs +15 -0
- package/js/examples/reproduce-issue-135.mjs +17 -0
- package/js/examples/shell-cd-behavior.mjs +88 -0
- package/js/examples/sigint-forwarding-test.mjs +60 -0
- package/js/examples/sigint-handler-test.mjs +72 -0
- package/js/examples/simple-async-test.mjs +49 -0
- package/js/examples/simple-claude-test.mjs +17 -0
- package/js/examples/simple-event-test.mjs +33 -0
- package/js/examples/simple-jq-streaming.mjs +48 -0
- package/js/examples/simple-stream-demo.mjs +35 -0
- package/js/examples/simple-test-sleep.js +30 -0
- package/js/examples/simple-working-stdin.mjs +30 -0
- package/js/examples/streaming-behavior-test.mjs +116 -0
- package/js/examples/streaming-direct-command.mjs +21 -0
- package/js/examples/streaming-filtered-output.mjs +33 -0
- package/js/examples/streaming-grep-pipeline.mjs +21 -0
- package/js/examples/streaming-interactive-stdin.mjs +24 -0
- package/js/examples/streaming-jq-pipeline.mjs +23 -0
- package/js/examples/streaming-multistage-pipeline.mjs +23 -0
- package/js/examples/streaming-pipes-event-pattern.mjs +27 -0
- package/js/examples/streaming-pipes-multistage.mjs +22 -0
- package/js/examples/streaming-pipes-realtime-jq.mjs +23 -0
- package/js/examples/streaming-progress-tracking.mjs +34 -0
- package/js/examples/streaming-reusable-configs.mjs +52 -0
- package/js/examples/streaming-silent-capture.mjs +20 -0
- package/js/examples/streaming-test-simple.mjs +70 -0
- package/js/examples/streaming-virtual-pipeline.mjs +18 -0
- package/js/examples/syntax-basic-comparison.mjs +31 -0
- package/js/examples/syntax-basic-options.mjs +12 -0
- package/js/examples/syntax-combined-options.mjs +19 -0
- package/js/examples/syntax-command-chaining.mjs +12 -0
- package/js/examples/syntax-custom-directory.mjs +10 -0
- package/js/examples/syntax-custom-environment.mjs +13 -0
- package/js/examples/syntax-custom-stdin.mjs +10 -0
- package/js/examples/syntax-mixed-regular.mjs +11 -0
- package/js/examples/syntax-mixed-usage.mjs +15 -0
- package/js/examples/syntax-multiple-listeners.mjs +87 -0
- package/js/examples/syntax-piping-comparison.mjs +32 -0
- package/js/examples/syntax-reusable-config.mjs +16 -0
- package/js/examples/syntax-reusable-configs.mjs +21 -0
- package/js/examples/syntax-silent-operations.mjs +10 -0
- package/js/examples/syntax-stdin-option.mjs +12 -0
- package/js/examples/temp-sigint-test.mjs +21 -0
- package/js/examples/test-actual-buildshell.mjs +44 -0
- package/js/examples/test-async-streams-working.mjs +102 -0
- package/js/examples/test-async-streams.mjs +90 -0
- package/js/examples/test-auth-parse.mjs +74 -0
- package/js/examples/test-auto-quoting.mjs +57 -0
- package/js/examples/test-auto-start-fix.mjs +95 -0
- package/js/examples/test-baseline-sigint.mjs +38 -0
- package/js/examples/test-buffer-behavior.mjs +39 -0
- package/js/examples/test-buffers-simple.mjs +35 -0
- package/js/examples/test-bun-specific-issue.mjs +106 -0
- package/js/examples/test-bun-streaming.mjs +81 -0
- package/js/examples/test-cat-direct.mjs +41 -0
- package/js/examples/test-cat-pipe.mjs +34 -0
- package/js/examples/test-cd-behavior.mjs +42 -0
- package/js/examples/test-child-process-timing.mjs +53 -0
- package/js/examples/test-child-sigint-handler.mjs +62 -0
- package/js/examples/test-cleanup-simple.mjs +21 -0
- package/js/examples/test-comprehensive-tracing.mjs +58 -0
- package/js/examples/test-correct-space-handling.mjs +46 -0
- package/js/examples/test-ctrl-c-debug.mjs +44 -0
- package/js/examples/test-ctrl-c-inherit.mjs +30 -0
- package/js/examples/test-ctrl-c-sleep.mjs +31 -0
- package/js/examples/test-ctrl-c.mjs +17 -0
- package/js/examples/test-debug-new-options.mjs +55 -0
- package/js/examples/test-debug-pty.mjs +49 -0
- package/js/examples/test-debug-tee.mjs +38 -0
- package/js/examples/test-debug.mjs +25 -0
- package/js/examples/test-direct-jq.mjs +47 -0
- package/js/examples/test-direct-pipe-reading.mjs +119 -0
- package/js/examples/test-direct-pipe.sh +28 -0
- package/js/examples/test-double-quoting-prevention.mjs +138 -0
- package/js/examples/test-edge-cases-quoting.mjs +89 -0
- package/js/examples/test-events.mjs +37 -0
- package/js/examples/test-explicit-stdio.mjs +51 -0
- package/js/examples/test-final-streaming.mjs +71 -0
- package/js/examples/test-fix.mjs +71 -0
- package/js/examples/test-incremental-streaming.mjs +46 -0
- package/js/examples/test-individual-spawn.mjs +35 -0
- package/js/examples/test-inherit-stdout-not-stdin.mjs +133 -0
- package/js/examples/test-injection-protection.mjs +77 -0
- package/js/examples/test-interactive-streaming.mjs +140 -0
- package/js/examples/test-interactive-top.md +24 -0
- package/js/examples/test-interactive.mjs +17 -0
- package/js/examples/test-interpolation.mjs +14 -0
- package/js/examples/test-interrupt.mjs +40 -0
- package/js/examples/test-issue-135-comprehensive.mjs +41 -0
- package/js/examples/test-issue12-detailed.mjs +89 -0
- package/js/examples/test-issue12-exact.mjs +33 -0
- package/js/examples/test-jq-color.mjs +57 -0
- package/js/examples/test-jq-colors.mjs +41 -0
- package/js/examples/test-jq-compact.mjs +33 -0
- package/js/examples/test-jq-native.sh +10 -0
- package/js/examples/test-jq-pipeline-behavior.mjs +80 -0
- package/js/examples/test-jq-realtime.mjs +40 -0
- package/js/examples/test-manual-start.mjs +54 -0
- package/js/examples/test-mixed-quoting.mjs +88 -0
- package/js/examples/test-multi-stream.mjs +50 -0
- package/js/examples/test-multistage-debug.mjs +44 -0
- package/js/examples/test-native-spawn-vs-command-stream.mjs +154 -0
- package/js/examples/test-no-parse-pipeline.mjs +33 -0
- package/js/examples/test-non-virtual.mjs +52 -0
- package/js/examples/test-operators.mjs +53 -0
- package/js/examples/test-parent-continues.mjs +44 -0
- package/js/examples/test-path-interpolation.mjs +86 -0
- package/js/examples/test-ping-kill-and-stdin.mjs +98 -0
- package/js/examples/test-ping.mjs +12 -0
- package/js/examples/test-pty-spawn.mjs +101 -0
- package/js/examples/test-pty.mjs +38 -0
- package/js/examples/test-quote-behavior-summary.mjs +110 -0
- package/js/examples/test-quote-edge-cases.mjs +69 -0
- package/js/examples/test-quote-parsing.mjs +23 -0
- package/js/examples/test-raw-function.mjs +153 -0
- package/js/examples/test-raw-streaming.mjs +47 -0
- package/js/examples/test-readme-examples.mjs +142 -0
- package/js/examples/test-real-cat.mjs +28 -0
- package/js/examples/test-real-commands.mjs +21 -0
- package/js/examples/test-real-shell.mjs +31 -0
- package/js/examples/test-real-stdin-commands.mjs +160 -0
- package/js/examples/test-runner-batched.mjs +98 -0
- package/js/examples/test-runner-simple.mjs +80 -0
- package/js/examples/test-runner.mjs +67 -0
- package/js/examples/test-scope-parse.mjs +31 -0
- package/js/examples/test-sh-pipeline.mjs +24 -0
- package/js/examples/test-shell-detection.mjs +71 -0
- package/js/examples/test-shell-parser.mjs +37 -0
- package/js/examples/test-sigint-behavior.mjs +241 -0
- package/js/examples/test-sigint-handling.sh +14 -0
- package/js/examples/test-simple-pipe.mjs +12 -0
- package/js/examples/test-simple-streaming.mjs +32 -0
- package/js/examples/test-sleep-stdin.js +27 -0
- package/js/examples/test-sleep.mjs +56 -0
- package/js/examples/test-smart-quoting.mjs +180 -0
- package/js/examples/test-spaces-in-path.mjs +48 -0
- package/js/examples/test-special-chars-quoting.mjs +54 -0
- package/js/examples/test-stdin-after-start.mjs +39 -0
- package/js/examples/test-stdin-simple.mjs +67 -0
- package/js/examples/test-stdin-timing.mjs +74 -0
- package/js/examples/test-stdio-combinations.mjs +124 -0
- package/js/examples/test-stream-access.mjs +84 -0
- package/js/examples/test-stream-cleanup.mjs +27 -0
- package/js/examples/test-stream-readers.mjs +152 -0
- package/js/examples/test-streaming-final.mjs +57 -0
- package/js/examples/test-streaming-interfaces.mjs +141 -0
- package/js/examples/test-streaming-timing.mjs +27 -0
- package/js/examples/test-streaming.mjs +32 -0
- package/js/examples/test-streams-stdin-comprehensive.mjs +134 -0
- package/js/examples/test-streams-stdin-ctrl-c.mjs +96 -0
- package/js/examples/test-template-literal.mjs +26 -0
- package/js/examples/test-template-vs-interpolation.mjs +49 -0
- package/js/examples/test-timing.mjs +41 -0
- package/js/examples/test-top-inherit-stdout-stdin-control.mjs +123 -0
- package/js/examples/test-top-quit-stdin.mjs +118 -0
- package/js/examples/test-trace-option.mjs +21 -0
- package/js/examples/test-user-double-quotes.mjs +36 -0
- package/js/examples/test-user-single-quotes.mjs +36 -0
- package/js/examples/test-verbose.mjs +18 -0
- package/js/examples/test-verbose2.mjs +32 -0
- package/js/examples/test-virtual-streaming.mjs +125 -0
- package/js/examples/test-waiting-command.mjs +52 -0
- package/js/examples/test-waiting-commands.mjs +83 -0
- package/js/examples/test-watch-mode.mjs +104 -0
- package/js/examples/test-yes-cancellation.mjs +26 -0
- package/js/examples/test-yes-detailed.mjs +58 -0
- package/js/examples/test-yes-trace.mjs +28 -0
- package/js/examples/trace-abort-controller.mjs +30 -0
- package/js/examples/trace-error-handling.mjs +22 -0
- package/js/examples/trace-pipeline-command.mjs +22 -0
- package/js/examples/trace-signal-handling.mjs +35 -0
- package/js/examples/trace-simple-command.mjs +18 -0
- package/js/examples/trace-stderr-output.mjs +22 -0
- package/js/examples/verify-fix-both-runtimes.mjs +73 -0
- package/js/examples/verify-issue12-fixed.mjs +78 -0
- package/js/examples/which-command-common-commands.mjs +19 -0
- package/js/examples/which-command-gh-test.mjs +23 -0
- package/js/examples/which-command-nonexistent.mjs +20 -0
- package/js/examples/which-command-system-comparison.mjs +28 -0
- package/js/examples/working-example.mjs +13 -0
- package/js/examples/working-stdin-examples.mjs +138 -0
- package/js/examples/working-streaming-demo.mjs +49 -0
- package/{src → js/src}/$.mjs +20 -4
- package/{src → js/src}/$.utils.mjs +14 -2
- package/js/tests/$.features.test.mjs +283 -0
- package/js/tests/$.test.mjs +935 -0
- package/js/tests/builtin-commands.test.mjs +387 -0
- package/js/tests/bun-shell-path-fix.test.mjs +115 -0
- package/js/tests/bun.features.test.mjs +189 -0
- package/js/tests/cd-virtual-command.test.mjs +622 -0
- package/js/tests/cleanup-verification.test.mjs +127 -0
- package/js/tests/ctrl-c-baseline.test.mjs +207 -0
- package/js/tests/ctrl-c-basic.test.mjs +220 -0
- package/js/tests/ctrl-c-library.test.mjs +197 -0
- package/js/tests/ctrl-c-signal.test.mjs +915 -0
- package/js/tests/examples.test.mjs +252 -0
- package/js/tests/execa.features.test.mjs +198 -0
- package/js/tests/gh-commands.test.mjs +164 -0
- package/js/tests/gh-gist-operations.test.mjs +221 -0
- package/js/tests/git-gh-cd.test.mjs +466 -0
- package/js/tests/interactive-option.test.mjs +114 -0
- package/js/tests/interactive-streaming.test.mjs +307 -0
- package/js/tests/issue-135-final.test.mjs +58 -0
- package/js/tests/jq-color-behavior.test.mjs +140 -0
- package/js/tests/jq.test.mjs +318 -0
- package/js/tests/options-examples.test.mjs +106 -0
- package/js/tests/options-syntax.test.mjs +112 -0
- package/js/tests/path-interpolation.test.mjs +412 -0
- package/js/tests/pipe.test.mjs +291 -0
- package/js/tests/raw-function.test.mjs +266 -0
- package/js/tests/readme-examples.test.mjs +427 -0
- package/js/tests/resource-cleanup-internals.test.mjs +669 -0
- package/js/tests/shell-settings.test.mjs +279 -0
- package/js/tests/sigint-cleanup-isolated.test.mjs +151 -0
- package/js/tests/sigint-cleanup.test.mjs +118 -0
- package/js/tests/start-run-edge-cases.test.mjs +152 -0
- package/js/tests/start-run-options.test.mjs +181 -0
- package/js/tests/stderr-output-handling.test.mjs +279 -0
- package/js/tests/streaming-interfaces.test.mjs +194 -0
- package/js/tests/sync.test.mjs +297 -0
- package/js/tests/system-pipe.test.mjs +226 -0
- package/js/tests/test-cleanup.mjs +200 -0
- package/js/tests/test-helper-fixed.mjs +148 -0
- package/js/tests/test-helper-v2.mjs +118 -0
- package/js/tests/test-helper.mjs +171 -0
- package/js/tests/test-sigint-child.js +15 -0
- package/js/tests/text-method.test.mjs +225 -0
- package/js/tests/virtual.test.mjs +364 -0
- package/js/tests/yes-command-cleanup.test.mjs +208 -0
- package/js/tests/zx.features.test.mjs +233 -0
- package/package.json +13 -12
- package/rust/Cargo.lock +947 -0
- package/rust/Cargo.toml +47 -0
- package/rust/src/commands/basename.rs +69 -0
- package/rust/src/commands/cat.rs +123 -0
- package/rust/src/commands/cd.rs +67 -0
- package/rust/src/commands/cp.rs +187 -0
- package/rust/src/commands/dirname.rs +57 -0
- package/rust/src/commands/echo.rs +73 -0
- package/rust/src/commands/env.rs +33 -0
- package/rust/src/commands/exit.rs +36 -0
- package/rust/src/commands/false.rs +24 -0
- package/rust/src/commands/ls.rs +182 -0
- package/rust/src/commands/mkdir.rs +98 -0
- package/rust/src/commands/mod.rs +200 -0
- package/rust/src/commands/mv.rs +180 -0
- package/rust/src/commands/pwd.rs +28 -0
- package/rust/src/commands/rm.rs +150 -0
- package/rust/src/commands/seq.rs +179 -0
- package/rust/src/commands/sleep.rs +97 -0
- package/rust/src/commands/test.rs +204 -0
- package/rust/src/commands/touch.rs +99 -0
- package/rust/src/commands/true.rs +24 -0
- package/rust/src/commands/which.rs +87 -0
- package/rust/src/commands/yes.rs +99 -0
- package/rust/src/lib.rs +492 -0
- package/rust/src/main.rs +37 -0
- package/rust/src/shell_parser.rs +565 -0
- package/rust/src/utils.rs +335 -0
- package/rust/tests/builtin_commands.rs +549 -0
- package/rust/tests/process_runner.rs +286 -0
- package/rust/tests/shell_parser.rs +296 -0
- package/rust/tests/utils.rs +282 -0
- package/rust/tests/virtual_commands.rs +199 -0
- /package/{src → js/src}/commands/$.basename.mjs +0 -0
- /package/{src → js/src}/commands/$.cat.mjs +0 -0
- /package/{src → js/src}/commands/$.cd.mjs +0 -0
- /package/{src → js/src}/commands/$.cp.mjs +0 -0
- /package/{src → js/src}/commands/$.dirname.mjs +0 -0
- /package/{src → js/src}/commands/$.echo.mjs +0 -0
- /package/{src → js/src}/commands/$.env.mjs +0 -0
- /package/{src → js/src}/commands/$.exit.mjs +0 -0
- /package/{src → js/src}/commands/$.false.mjs +0 -0
- /package/{src → js/src}/commands/$.ls.mjs +0 -0
- /package/{src → js/src}/commands/$.mkdir.mjs +0 -0
- /package/{src → js/src}/commands/$.mv.mjs +0 -0
- /package/{src → js/src}/commands/$.pwd.mjs +0 -0
- /package/{src → js/src}/commands/$.rm.mjs +0 -0
- /package/{src → js/src}/commands/$.seq.mjs +0 -0
- /package/{src → js/src}/commands/$.sleep.mjs +0 -0
- /package/{src → js/src}/commands/$.test.mjs +0 -0
- /package/{src → js/src}/commands/$.touch.mjs +0 -0
- /package/{src → js/src}/commands/$.true.mjs +0 -0
- /package/{src → js/src}/commands/$.which.mjs +0 -0
- /package/{src → js/src}/commands/$.yes.mjs +0 -0
- /package/{src → js/src}/shell-parser.mjs +0 -0
|
@@ -0,0 +1,915 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
|
|
2
|
+
import { beforeTestCleanup, afterTestCleanup } from './test-cleanup.mjs';
|
|
3
|
+
import { spawn } from 'child_process';
|
|
4
|
+
import { trace } from '../src/$.utils.mjs';
|
|
5
|
+
|
|
6
|
+
// Platform detection - Windows handles signals differently than Unix
|
|
7
|
+
const isWindows = process.platform === 'win32';
|
|
8
|
+
|
|
9
|
+
// Skip entire describe block on Windows - SIGINT/signal handling is fundamentally different
|
|
10
|
+
describe.skipIf(isWindows)('CTRL+C Signal Handling', () => {
|
|
11
|
+
let childProcesses = [];
|
|
12
|
+
|
|
13
|
+
beforeEach(async () => {
|
|
14
|
+
await beforeTestCleanup();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// Log platform information for debugging
|
|
18
|
+
trace('SignalTest', () => `Platform: ${process.platform}`);
|
|
19
|
+
trace('SignalTest', () => `OS Release: ${require('os').release()}`);
|
|
20
|
+
trace('SignalTest', () => `Node Version: ${process.version}`);
|
|
21
|
+
trace('SignalTest', () => `CI Environment: ${process.env.CI || 'false'}`);
|
|
22
|
+
|
|
23
|
+
// Baseline test to verify that shell commands work in CI
|
|
24
|
+
it('BASELINE: should handle SIGINT with plain shell command', async () => {
|
|
25
|
+
trace(
|
|
26
|
+
'SignalTest',
|
|
27
|
+
() => `Starting baseline SIGINT test on ${process.platform}`
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const child = spawn('sh', ['-c', 'echo "BASELINE_START" && sleep 30'], {
|
|
31
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
32
|
+
detached: true,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
trace('SignalTest', () => `Baseline child spawned, PID: ${child.pid}`);
|
|
36
|
+
childProcesses.push(child);
|
|
37
|
+
|
|
38
|
+
let stdout = '';
|
|
39
|
+
child.stdout.on('data', (data) => {
|
|
40
|
+
stdout += data.toString();
|
|
41
|
+
trace(
|
|
42
|
+
'SignalTest',
|
|
43
|
+
() => `Baseline received stdout: ${JSON.stringify(data.toString())}`
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Wait for output
|
|
48
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
49
|
+
|
|
50
|
+
// Send SIGINT (on macOS with detached:true, might need SIGTERM)
|
|
51
|
+
child.kill('SIGINT');
|
|
52
|
+
|
|
53
|
+
// Wait for exit with timeout
|
|
54
|
+
const { code, signal } = await new Promise((resolve) => {
|
|
55
|
+
let resolved = false;
|
|
56
|
+
|
|
57
|
+
child.on('exit', (code, signal) => {
|
|
58
|
+
if (!resolved) {
|
|
59
|
+
resolved = true;
|
|
60
|
+
resolve({ code, signal });
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// On macOS, detached processes might not respond to SIGINT, use SIGTERM
|
|
65
|
+
setTimeout(() => {
|
|
66
|
+
if (!resolved) {
|
|
67
|
+
trace('SignalTest', 'SIGINT timeout, trying SIGTERM');
|
|
68
|
+
child.kill('SIGTERM');
|
|
69
|
+
}
|
|
70
|
+
}, 1000);
|
|
71
|
+
|
|
72
|
+
// Final fallback
|
|
73
|
+
setTimeout(() => {
|
|
74
|
+
if (!resolved) {
|
|
75
|
+
trace('SignalTest', 'SIGTERM timeout, using SIGKILL');
|
|
76
|
+
child.kill('SIGKILL');
|
|
77
|
+
}
|
|
78
|
+
}, 2000);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
trace('SignalTest', () => `Baseline exit code: ${code} signal: ${signal}`);
|
|
82
|
+
trace('SignalTest', () => `Baseline stdout: ${stdout}`);
|
|
83
|
+
|
|
84
|
+
expect(stdout).toContain('BASELINE_START');
|
|
85
|
+
// On macOS with detached:true, processes might need SIGTERM/SIGKILL
|
|
86
|
+
// On Linux, it typically exits with code 130 for SIGINT
|
|
87
|
+
const validExit =
|
|
88
|
+
code === 130 ||
|
|
89
|
+
code === 143 ||
|
|
90
|
+
code === 0 ||
|
|
91
|
+
signal === 'SIGINT' ||
|
|
92
|
+
signal === 'SIGTERM' ||
|
|
93
|
+
signal === 'SIGKILL';
|
|
94
|
+
expect(validExit).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
afterEach(async () => {
|
|
98
|
+
// Clean up any remaining child processes
|
|
99
|
+
childProcesses.forEach((child) => {
|
|
100
|
+
if (!child.killed) {
|
|
101
|
+
child.kill('SIGKILL');
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
childProcesses = [];
|
|
105
|
+
|
|
106
|
+
// Run test cleanup
|
|
107
|
+
await afterTestCleanup();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it(
|
|
111
|
+
'should forward SIGINT to child process when external CTRL+C is sent',
|
|
112
|
+
async () => {
|
|
113
|
+
trace('SignalTest', 'Starting SIGINT forwarding test');
|
|
114
|
+
trace('SignalTest', () => `Current working directory: ${process.cwd()}`);
|
|
115
|
+
|
|
116
|
+
// Check if file exists first
|
|
117
|
+
const fs = await import('fs');
|
|
118
|
+
const path = await import('path');
|
|
119
|
+
const scriptPath = path.join(
|
|
120
|
+
process.cwd(),
|
|
121
|
+
'js/examples',
|
|
122
|
+
'test-sleep.mjs'
|
|
123
|
+
);
|
|
124
|
+
trace('SignalTest', () => `Script path: ${scriptPath}`);
|
|
125
|
+
trace('SignalTest', () => `Script exists: ${fs.existsSync(scriptPath)}`);
|
|
126
|
+
|
|
127
|
+
// Use the test-sleep.mjs which tests our actual library
|
|
128
|
+
const child = spawn('node', [scriptPath], {
|
|
129
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
130
|
+
detached: true,
|
|
131
|
+
cwd: process.cwd(), // Explicitly set working directory
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
trace('SignalTest', () => `Child process spawned, PID: ${child.pid}`);
|
|
135
|
+
childProcesses.push(child);
|
|
136
|
+
|
|
137
|
+
let stdout = '';
|
|
138
|
+
let stderr = '';
|
|
139
|
+
let dataReceived = false;
|
|
140
|
+
const stdoutChunks = [];
|
|
141
|
+
|
|
142
|
+
child.stdout.on('data', (data) => {
|
|
143
|
+
const chunk = data.toString();
|
|
144
|
+
stdout += chunk;
|
|
145
|
+
stdoutChunks.push({ time: Date.now(), data: chunk });
|
|
146
|
+
dataReceived = true;
|
|
147
|
+
trace(
|
|
148
|
+
'SignalTest',
|
|
149
|
+
() => `Received stdout chunk: ${JSON.stringify(chunk)}`
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
child.stderr.on('data', (data) => {
|
|
154
|
+
const chunk = data.toString();
|
|
155
|
+
stderr += chunk;
|
|
156
|
+
trace('SignalTest', () => `Child stderr: ${chunk.trim()}`);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
child.on('error', (error) => {
|
|
160
|
+
trace('SignalTest', () => `Child process error: ${error.message}`);
|
|
161
|
+
trace('SignalTest', () => `Error stack: ${error.stack}`);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
child.on('spawn', () => {
|
|
165
|
+
trace('SignalTest', 'Child process spawned successfully');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Wait for the process to start and output data
|
|
169
|
+
let attempts = 0;
|
|
170
|
+
while (!dataReceived && attempts < 20) {
|
|
171
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
172
|
+
attempts++;
|
|
173
|
+
if (attempts % 5 === 0) {
|
|
174
|
+
trace('SignalTest', () => `Waiting for stdout, attempt: ${attempts}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
trace(
|
|
179
|
+
'SignalTest',
|
|
180
|
+
() => `Data received: ${dataReceived} after attempts: ${attempts}`
|
|
181
|
+
);
|
|
182
|
+
trace('SignalTest', () => `Current stdout length: ${stdout.length}`);
|
|
183
|
+
trace('SignalTest', () => `Current stderr length: ${stderr.length}`);
|
|
184
|
+
|
|
185
|
+
// Additional wait to ensure process is fully running
|
|
186
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
187
|
+
|
|
188
|
+
// Send SIGINT to the child process (simulating CTRL+C)
|
|
189
|
+
trace('SignalTest', 'Sending SIGINT to child process');
|
|
190
|
+
const killResult = child.kill('SIGINT');
|
|
191
|
+
trace('SignalTest', () => `Kill result: ${killResult}`);
|
|
192
|
+
|
|
193
|
+
// Wait for the process to exit with robust handling
|
|
194
|
+
const exitCode = await new Promise((resolve) => {
|
|
195
|
+
let resolved = false;
|
|
196
|
+
|
|
197
|
+
child.on('close', (code, signal) => {
|
|
198
|
+
trace(
|
|
199
|
+
'SignalTest',
|
|
200
|
+
() => `Child closed with code: ${code} signal: ${signal}`
|
|
201
|
+
);
|
|
202
|
+
if (!resolved) {
|
|
203
|
+
resolved = true;
|
|
204
|
+
resolve(code !== null ? code : signal === 'SIGINT' ? 130 : 1);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
child.on('exit', (code, signal) => {
|
|
209
|
+
trace(
|
|
210
|
+
'SignalTest',
|
|
211
|
+
() => `Child exited with code: ${code} signal: ${signal}`
|
|
212
|
+
);
|
|
213
|
+
if (!resolved) {
|
|
214
|
+
resolved = true;
|
|
215
|
+
resolve(code !== null ? code : signal === 'SIGINT' ? 130 : 1);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Fallback timeout
|
|
220
|
+
setTimeout(() => {
|
|
221
|
+
if (!resolved) {
|
|
222
|
+
trace('SignalTest', 'Timeout reached, force killing child');
|
|
223
|
+
resolved = true;
|
|
224
|
+
child.kill('SIGKILL');
|
|
225
|
+
resolve(137);
|
|
226
|
+
}
|
|
227
|
+
}, 3000);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Should exit with SIGINT code (130) due to our signal handling
|
|
231
|
+
trace('SignalTest', () => `First test exit code: ${exitCode}`);
|
|
232
|
+
trace('SignalTest', () => `First test stdout length: ${stdout.length}`);
|
|
233
|
+
if (stdout.length === 0) {
|
|
234
|
+
trace('SignalTest', 'WARNING: No stdout captured!');
|
|
235
|
+
trace('SignalTest', () => `stderr content: ${stderr}`);
|
|
236
|
+
trace(
|
|
237
|
+
'SignalTest',
|
|
238
|
+
() => `stdout chunks received: ${JSON.stringify(stdoutChunks)}`
|
|
239
|
+
);
|
|
240
|
+
} else {
|
|
241
|
+
trace('SignalTest', () => `stdout content: ${JSON.stringify(stdout)}`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
expect([130, 143, 137].includes(exitCode) || exitCode > 0).toBe(true);
|
|
245
|
+
|
|
246
|
+
// Should have started sleep successfully before being interrupted
|
|
247
|
+
expect(stdout).toContain('STARTING_SLEEP');
|
|
248
|
+
},
|
|
249
|
+
{ timeout: 10000 }
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
it(
|
|
253
|
+
'should not interfere with user SIGINT handling when no children active',
|
|
254
|
+
async () => {
|
|
255
|
+
// Use inline Node.js code for better CI reliability
|
|
256
|
+
const nodeCode = `
|
|
257
|
+
process.on('SIGINT', () => {
|
|
258
|
+
console.log('USER_SIGINT_HANDLER_CALLED');
|
|
259
|
+
process.exit(42);
|
|
260
|
+
});
|
|
261
|
+
console.log('Process started, waiting for SIGINT...');
|
|
262
|
+
setTimeout(() => {
|
|
263
|
+
console.log('TIMEOUT_REACHED');
|
|
264
|
+
process.exit(1);
|
|
265
|
+
}, 5000);
|
|
266
|
+
`;
|
|
267
|
+
const child = spawn('node', ['-e', nodeCode], {
|
|
268
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
269
|
+
detached: true,
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
childProcesses.push(child);
|
|
273
|
+
|
|
274
|
+
let stdout = '';
|
|
275
|
+
let stderr = '';
|
|
276
|
+
|
|
277
|
+
child.stdout.on('data', (data) => {
|
|
278
|
+
stdout += data.toString();
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
child.stderr.on('data', (data) => {
|
|
282
|
+
stderr += data.toString();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Give the process time to set up its signal handler
|
|
286
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
287
|
+
|
|
288
|
+
// Send SIGINT to the process
|
|
289
|
+
child.kill('SIGINT');
|
|
290
|
+
|
|
291
|
+
// Wait for the process to exit with robust handling
|
|
292
|
+
const exitCode = await new Promise((resolve) => {
|
|
293
|
+
let resolved = false;
|
|
294
|
+
|
|
295
|
+
child.on('close', (code, signal) => {
|
|
296
|
+
if (!resolved) {
|
|
297
|
+
resolved = true;
|
|
298
|
+
resolve(code !== null ? code : signal === 'SIGINT' ? 130 : 1);
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
child.on('exit', (code, signal) => {
|
|
303
|
+
if (!resolved) {
|
|
304
|
+
resolved = true;
|
|
305
|
+
resolve(code !== null ? code : signal === 'SIGINT' ? 130 : 1);
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// Fallback timeout
|
|
310
|
+
setTimeout(() => {
|
|
311
|
+
if (!resolved) {
|
|
312
|
+
resolved = true;
|
|
313
|
+
child.kill('SIGKILL');
|
|
314
|
+
resolve(137);
|
|
315
|
+
}
|
|
316
|
+
}, 3000);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// Should exit with user's custom exit code (42)
|
|
320
|
+
expect(exitCode).toBe(42);
|
|
321
|
+
expect(stdout).toContain('USER_SIGINT_HANDLER_CALLED');
|
|
322
|
+
expect(stdout).not.toContain('TIMEOUT_REACHED');
|
|
323
|
+
},
|
|
324
|
+
{ timeout: 5000 }
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
it(
|
|
328
|
+
'should handle SIGINT in long-running commands via API',
|
|
329
|
+
async () => {
|
|
330
|
+
// This test uses the $ API directly but doesn't send signals to the test process
|
|
331
|
+
const { $ } = await import('../src/$.mjs');
|
|
332
|
+
|
|
333
|
+
// Start a long-running command
|
|
334
|
+
const runner = $`sleep 10`;
|
|
335
|
+
const commandPromise = runner.start();
|
|
336
|
+
|
|
337
|
+
// Give it time to start
|
|
338
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
339
|
+
|
|
340
|
+
// Kill the runner directly (this simulates what happens when SIGINT is forwarded)
|
|
341
|
+
runner.kill();
|
|
342
|
+
|
|
343
|
+
// Wait for command to finish
|
|
344
|
+
const result = await commandPromise;
|
|
345
|
+
|
|
346
|
+
// Verify the command was interrupted with proper exit code
|
|
347
|
+
expect(result.code).toBe(143); // SIGTERM exit code
|
|
348
|
+
},
|
|
349
|
+
{ timeout: 10000 }
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
it(
|
|
353
|
+
'should handle multiple concurrent processes receiving signals',
|
|
354
|
+
async () => {
|
|
355
|
+
const { $ } = await import('../src/$.mjs');
|
|
356
|
+
const runners = [];
|
|
357
|
+
const promises = [];
|
|
358
|
+
|
|
359
|
+
// Start multiple long-running commands
|
|
360
|
+
for (let i = 0; i < 3; i++) {
|
|
361
|
+
const runner = $`sleep 10`;
|
|
362
|
+
runners.push(runner);
|
|
363
|
+
promises.push(runner.start());
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Give them time to start
|
|
367
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
368
|
+
|
|
369
|
+
// Kill all runners (simulating SIGINT forwarding)
|
|
370
|
+
runners.forEach((runner) => runner.kill());
|
|
371
|
+
|
|
372
|
+
// Wait for all to finish
|
|
373
|
+
const results = await Promise.all(promises);
|
|
374
|
+
|
|
375
|
+
// All should have been interrupted with proper exit code
|
|
376
|
+
expect(results.length).toBe(3);
|
|
377
|
+
results.forEach((result) => {
|
|
378
|
+
expect(result.code).toBe(143); // SIGTERM exit code
|
|
379
|
+
});
|
|
380
|
+
},
|
|
381
|
+
{ timeout: 10000 }
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
it(
|
|
385
|
+
'should properly handle signals in external process with sleep',
|
|
386
|
+
async () => {
|
|
387
|
+
// Use a simple shell script for CI reliability
|
|
388
|
+
const child = spawn(
|
|
389
|
+
'sh',
|
|
390
|
+
['-c', 'echo "STARTING_SLEEP" && sleep 10 && echo "SLEEP_COMPLETED"'],
|
|
391
|
+
{
|
|
392
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
393
|
+
detached: true,
|
|
394
|
+
}
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
childProcesses.push(child);
|
|
398
|
+
|
|
399
|
+
let stdout = '';
|
|
400
|
+
let stderr = '';
|
|
401
|
+
|
|
402
|
+
child.stdout.on('data', (data) => {
|
|
403
|
+
stdout += data.toString();
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
child.stderr.on('data', (data) => {
|
|
407
|
+
stderr += data.toString();
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// Wait for sleep to start
|
|
411
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
412
|
+
|
|
413
|
+
// Send SIGINT to the process
|
|
414
|
+
child.kill('SIGINT');
|
|
415
|
+
|
|
416
|
+
// Wait for the process to exit with robust handling
|
|
417
|
+
const exitCode = await new Promise((resolve) => {
|
|
418
|
+
let resolved = false;
|
|
419
|
+
|
|
420
|
+
child.on('close', (code, signal) => {
|
|
421
|
+
if (!resolved) {
|
|
422
|
+
resolved = true;
|
|
423
|
+
resolve(code !== null ? code : signal === 'SIGINT' ? 130 : 1);
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
child.on('exit', (code, signal) => {
|
|
428
|
+
if (!resolved) {
|
|
429
|
+
resolved = true;
|
|
430
|
+
resolve(code !== null ? code : signal === 'SIGINT' ? 130 : 1);
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
// Fallback timeout
|
|
435
|
+
setTimeout(() => {
|
|
436
|
+
if (!resolved) {
|
|
437
|
+
resolved = true;
|
|
438
|
+
child.kill('SIGKILL');
|
|
439
|
+
resolve(137);
|
|
440
|
+
}
|
|
441
|
+
}, 3000);
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// Should exit with SIGINT code due to our signal handling
|
|
445
|
+
trace('SignalTest', () => `Third test exit code: ${exitCode}`);
|
|
446
|
+
expect([130, 143, 137].includes(exitCode) || exitCode > 0).toBe(true);
|
|
447
|
+
expect(stdout).toContain('STARTING_SLEEP');
|
|
448
|
+
expect(stdout).not.toContain('SLEEP_COMPLETED');
|
|
449
|
+
},
|
|
450
|
+
{ timeout: 10000 }
|
|
451
|
+
);
|
|
452
|
+
|
|
453
|
+
it(
|
|
454
|
+
'should not interfere with child process signal handlers',
|
|
455
|
+
async () => {
|
|
456
|
+
// Create a script that has its own SIGINT handler for cleanup (no ES modules)
|
|
457
|
+
const nodeCode = `
|
|
458
|
+
let cleanupDone = false;
|
|
459
|
+
process.on('SIGINT', () => {
|
|
460
|
+
console.log('CHILD_CLEANUP_START');
|
|
461
|
+
// Simulate cleanup work
|
|
462
|
+
setTimeout(() => {
|
|
463
|
+
cleanupDone = true;
|
|
464
|
+
console.log('CHILD_CLEANUP_DONE');
|
|
465
|
+
process.exit(0); // Exit cleanly after cleanup
|
|
466
|
+
}, 100);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
console.log('CHILD_READY');
|
|
470
|
+
|
|
471
|
+
// Keep process alive
|
|
472
|
+
setTimeout(() => {
|
|
473
|
+
console.log('TIMEOUT_REACHED');
|
|
474
|
+
process.exit(1);
|
|
475
|
+
}, 5000);
|
|
476
|
+
`;
|
|
477
|
+
const child = spawn('node', ['-e', nodeCode], {
|
|
478
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
479
|
+
detached: true,
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
childProcesses.push(child);
|
|
483
|
+
|
|
484
|
+
let stdout = '';
|
|
485
|
+
let stderr = '';
|
|
486
|
+
|
|
487
|
+
child.stdout.on('data', (data) => {
|
|
488
|
+
stdout += data.toString();
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
child.stderr.on('data', (data) => {
|
|
492
|
+
stderr += data.toString();
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
// Wait for child to be ready
|
|
496
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
497
|
+
|
|
498
|
+
// Send SIGINT to the process
|
|
499
|
+
child.kill('SIGINT');
|
|
500
|
+
|
|
501
|
+
// Wait for the process to exit with robust handling
|
|
502
|
+
const exitCode = await new Promise((resolve) => {
|
|
503
|
+
let resolved = false;
|
|
504
|
+
|
|
505
|
+
child.on('close', (code, signal) => {
|
|
506
|
+
if (!resolved) {
|
|
507
|
+
resolved = true;
|
|
508
|
+
resolve(code !== null ? code : signal === 'SIGINT' ? 130 : 1);
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
child.on('exit', (code, signal) => {
|
|
513
|
+
if (!resolved) {
|
|
514
|
+
resolved = true;
|
|
515
|
+
resolve(code !== null ? code : signal === 'SIGINT' ? 130 : 1);
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
// Fallback timeout
|
|
520
|
+
setTimeout(() => {
|
|
521
|
+
if (!resolved) {
|
|
522
|
+
resolved = true;
|
|
523
|
+
child.kill('SIGKILL');
|
|
524
|
+
resolve(137);
|
|
525
|
+
}
|
|
526
|
+
}, 3000);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
// Should exit with exit code 0 since child has its own SIGINT handler that calls process.exit(0)
|
|
530
|
+
// The child should have started its cleanup handler
|
|
531
|
+
trace('SignalTest', () => `Fourth test exit code: ${exitCode}`);
|
|
532
|
+
expect(exitCode).toBe(0); // This test is specifically for children with custom signal handlers
|
|
533
|
+
expect(stdout).toContain('CHILD_READY');
|
|
534
|
+
expect(stdout).toContain('CHILD_CLEANUP_START');
|
|
535
|
+
// Note: CHILD_CLEANUP_DONE might not appear if the process is killed during cleanup
|
|
536
|
+
// This is realistic behavior when external SIGINT is sent
|
|
537
|
+
},
|
|
538
|
+
{ timeout: 10000 }
|
|
539
|
+
);
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
// Skip entire describe block on Windows - uses 'sh' shell and Unix signals
|
|
543
|
+
describe.skipIf(isWindows)('CTRL+C with Different stdin Modes', () => {
|
|
544
|
+
let childProcesses = [];
|
|
545
|
+
|
|
546
|
+
beforeEach(async () => {
|
|
547
|
+
await beforeTestCleanup();
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
afterEach(async () => {
|
|
551
|
+
// Clean up any remaining child processes
|
|
552
|
+
childProcesses.forEach((child) => {
|
|
553
|
+
if (!child.killed) {
|
|
554
|
+
child.kill('SIGKILL');
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
childProcesses = [];
|
|
558
|
+
|
|
559
|
+
// Run test cleanup
|
|
560
|
+
await afterTestCleanup();
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
it(
|
|
564
|
+
'should handle kill regardless of stdin mode',
|
|
565
|
+
async () => {
|
|
566
|
+
const { $ } = await import('../src/$.mjs');
|
|
567
|
+
|
|
568
|
+
// Test 1: Default stdin (inherit)
|
|
569
|
+
const runner1 = $`sleep 3`;
|
|
570
|
+
const promise1 = runner1.start();
|
|
571
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
572
|
+
runner1.kill();
|
|
573
|
+
const result1 = await promise1;
|
|
574
|
+
expect(result1.code).toBe(143); // SIGTERM exit code
|
|
575
|
+
|
|
576
|
+
// Test 2: With stdin set to a string using new syntax
|
|
577
|
+
const runner2 = $({ stdin: 'some input data' })`sleep 3`;
|
|
578
|
+
const promise2 = runner2.start();
|
|
579
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
580
|
+
runner2.kill();
|
|
581
|
+
const result2 = await promise2;
|
|
582
|
+
expect(result2.code).toBe(143); // SIGTERM exit code
|
|
583
|
+
|
|
584
|
+
// Test 3: With stdin set to ignore using new syntax
|
|
585
|
+
const runner3 = $({ stdin: 'ignore' })`sleep 3`;
|
|
586
|
+
const promise3 = runner3.start();
|
|
587
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
588
|
+
runner3.kill();
|
|
589
|
+
const result3 = await promise3;
|
|
590
|
+
expect(result3.code).toBe(143); // SIGTERM exit code
|
|
591
|
+
},
|
|
592
|
+
{ timeout: 15000 }
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
it(
|
|
596
|
+
'should properly clean up stdin forwarding on external SIGINT',
|
|
597
|
+
async () => {
|
|
598
|
+
// Test that stdin forwarding is properly cleaned up when external SIGINT is sent
|
|
599
|
+
// Use simple shell command for CI reliability
|
|
600
|
+
const child = spawn('sh', ['-c', 'echo "RUNNING_COMMAND" && sleep 3'], {
|
|
601
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
602
|
+
detached: true,
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
childProcesses.push(child);
|
|
606
|
+
|
|
607
|
+
let stdout = '';
|
|
608
|
+
let stderr = '';
|
|
609
|
+
|
|
610
|
+
child.stdout.on('data', (data) => {
|
|
611
|
+
stdout += data.toString();
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
child.stderr.on('data', (data) => {
|
|
615
|
+
stderr += data.toString();
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
// Give it time to set up stdin forwarding
|
|
619
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
620
|
+
|
|
621
|
+
// Send SIGINT to interrupt the cat command
|
|
622
|
+
child.kill('SIGINT');
|
|
623
|
+
|
|
624
|
+
// Wait for the process to exit with robust handling
|
|
625
|
+
const exitCode = await new Promise((resolve) => {
|
|
626
|
+
let resolved = false;
|
|
627
|
+
|
|
628
|
+
child.on('close', (code, signal) => {
|
|
629
|
+
if (!resolved) {
|
|
630
|
+
resolved = true;
|
|
631
|
+
resolve(code !== null ? code : signal === 'SIGINT' ? 130 : 1);
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
child.on('exit', (code, signal) => {
|
|
636
|
+
if (!resolved) {
|
|
637
|
+
resolved = true;
|
|
638
|
+
resolve(code !== null ? code : signal === 'SIGINT' ? 130 : 1);
|
|
639
|
+
}
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
// Fallback timeout
|
|
643
|
+
setTimeout(() => {
|
|
644
|
+
if (!resolved) {
|
|
645
|
+
resolved = true;
|
|
646
|
+
child.kill('SIGKILL');
|
|
647
|
+
resolve(137);
|
|
648
|
+
}
|
|
649
|
+
}, 3000);
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
// Should exit with SIGINT code or clean exit (both are acceptable for stdin cleanup tests)
|
|
653
|
+
trace('SignalTest', () => `Fifth test exit code: ${exitCode}`);
|
|
654
|
+
expect(typeof exitCode).toBe('number'); // Just ensure we get a valid exit code
|
|
655
|
+
expect(stdout).toContain('RUNNING_COMMAND');
|
|
656
|
+
// The important thing is that the process is properly cleaned up
|
|
657
|
+
// This is handled by our signal forwarding cleanup
|
|
658
|
+
},
|
|
659
|
+
{ timeout: 10000 }
|
|
660
|
+
);
|
|
661
|
+
|
|
662
|
+
it(
|
|
663
|
+
'should handle parent stream closure triggering process cleanup',
|
|
664
|
+
async () => {
|
|
665
|
+
// Test parent stream closure handling mechanism
|
|
666
|
+
const child = spawn(
|
|
667
|
+
'node',
|
|
668
|
+
[
|
|
669
|
+
'-e',
|
|
670
|
+
`
|
|
671
|
+
import { $ } from './js/src/$.mjs';
|
|
672
|
+
|
|
673
|
+
// Start a long-running command
|
|
674
|
+
const runner = $\`sleep 5\`;
|
|
675
|
+
const promise = runner.start();
|
|
676
|
+
|
|
677
|
+
// Simulate parent stream closure after a delay
|
|
678
|
+
setTimeout(() => {
|
|
679
|
+
console.log('SIMULATING_PARENT_STREAM_CLOSURE');
|
|
680
|
+
process.stdout.destroy(); // This should trigger cleanup
|
|
681
|
+
}, 1000);
|
|
682
|
+
|
|
683
|
+
try {
|
|
684
|
+
await promise;
|
|
685
|
+
console.log('COMMAND_COMPLETED');
|
|
686
|
+
} catch (error) {
|
|
687
|
+
console.log('COMMAND_INTERRUPTED');
|
|
688
|
+
}
|
|
689
|
+
`,
|
|
690
|
+
],
|
|
691
|
+
{
|
|
692
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
693
|
+
detached: true,
|
|
694
|
+
}
|
|
695
|
+
);
|
|
696
|
+
|
|
697
|
+
childProcesses.push(child);
|
|
698
|
+
|
|
699
|
+
let stdout = '';
|
|
700
|
+
child.stdout.on('data', (data) => {
|
|
701
|
+
stdout += data.toString();
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
// Wait for the process to complete
|
|
705
|
+
const exitCode = await new Promise((resolve) => {
|
|
706
|
+
child.on('close', (code) => {
|
|
707
|
+
resolve(code);
|
|
708
|
+
});
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
// Should have detected parent stream closure and exited
|
|
712
|
+
expect(stdout).toContain('SIMULATING_PARENT_STREAM_CLOSURE');
|
|
713
|
+
expect(typeof exitCode).toBe('number'); // Should have a valid exit code
|
|
714
|
+
},
|
|
715
|
+
{ timeout: 10000 }
|
|
716
|
+
);
|
|
717
|
+
|
|
718
|
+
it(
|
|
719
|
+
'should bypass virtual commands with custom stdin for proper signal handling',
|
|
720
|
+
async () => {
|
|
721
|
+
// Test the bypass logic for built-in commands with custom stdin
|
|
722
|
+
const child = spawn(
|
|
723
|
+
'node',
|
|
724
|
+
[
|
|
725
|
+
'-e',
|
|
726
|
+
`
|
|
727
|
+
import { $ } from './js/src/$.mjs';
|
|
728
|
+
|
|
729
|
+
console.log('STARTING_SLEEP_WITH_CUSTOM_STDIN');
|
|
730
|
+
|
|
731
|
+
try {
|
|
732
|
+
// This should bypass virtual sleep and use real /usr/bin/sleep
|
|
733
|
+
const result = await \$({ stdin: 'custom input' })\`sleep 2\`;
|
|
734
|
+
console.log('SLEEP_COMPLETED: ' + result.code);
|
|
735
|
+
} catch (error) {
|
|
736
|
+
console.log('SLEEP_ERROR: ' + error.message);
|
|
737
|
+
}
|
|
738
|
+
`,
|
|
739
|
+
],
|
|
740
|
+
{
|
|
741
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
742
|
+
detached: true,
|
|
743
|
+
}
|
|
744
|
+
);
|
|
745
|
+
|
|
746
|
+
childProcesses.push(child);
|
|
747
|
+
|
|
748
|
+
let stdout = '';
|
|
749
|
+
child.stdout.on('data', (data) => {
|
|
750
|
+
stdout += data.toString();
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
// Give it time to start then interrupt
|
|
754
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
755
|
+
child.kill('SIGINT');
|
|
756
|
+
|
|
757
|
+
const exitCode = await new Promise((resolve) => {
|
|
758
|
+
child.on('close', (code) => {
|
|
759
|
+
resolve(code);
|
|
760
|
+
});
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
trace('SignalTest', () => `Sixth test exit code: ${exitCode}`);
|
|
764
|
+
expect([130, 143, 137].includes(exitCode) || exitCode > 0).toBe(true); // SIGINT exit code
|
|
765
|
+
expect(stdout).toContain('STARTING_SLEEP_WITH_CUSTOM_STDIN');
|
|
766
|
+
},
|
|
767
|
+
{ timeout: 10000 }
|
|
768
|
+
);
|
|
769
|
+
|
|
770
|
+
it(
|
|
771
|
+
'should handle Bun vs Node.js signal differences',
|
|
772
|
+
async () => {
|
|
773
|
+
// Test platform-specific signal handling
|
|
774
|
+
const child = spawn(
|
|
775
|
+
'node',
|
|
776
|
+
[
|
|
777
|
+
'-e',
|
|
778
|
+
`
|
|
779
|
+
import { $ } from './js/src/$.mjs';
|
|
780
|
+
|
|
781
|
+
const isBun = typeof globalThis.Bun !== 'undefined';
|
|
782
|
+
console.log('RUNTIME: ' + (isBun ? 'BUN' : 'NODE'));
|
|
783
|
+
|
|
784
|
+
try {
|
|
785
|
+
const result = await $\`sleep 2\`;
|
|
786
|
+
console.log('SLEEP_COMPLETED: ' + result.code);
|
|
787
|
+
} catch (error) {
|
|
788
|
+
console.log('SLEEP_ERROR: ' + error.message);
|
|
789
|
+
}
|
|
790
|
+
`,
|
|
791
|
+
],
|
|
792
|
+
{
|
|
793
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
794
|
+
detached: true,
|
|
795
|
+
}
|
|
796
|
+
);
|
|
797
|
+
|
|
798
|
+
childProcesses.push(child);
|
|
799
|
+
|
|
800
|
+
let stdout = '';
|
|
801
|
+
child.stdout.on('data', (data) => {
|
|
802
|
+
stdout += data.toString();
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
// Let it run for a bit then interrupt
|
|
806
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
807
|
+
child.kill('SIGINT');
|
|
808
|
+
|
|
809
|
+
const exitCode = await new Promise((resolve) => {
|
|
810
|
+
child.on('close', (code) => {
|
|
811
|
+
resolve(code);
|
|
812
|
+
});
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
trace('SignalTest', () => `Seventh test exit code: ${exitCode}`);
|
|
816
|
+
expect(typeof exitCode).toBe('number'); // Platform differences may result in various exit codes
|
|
817
|
+
expect(stdout).toMatch(/RUNTIME: (BUN|NODE)/);
|
|
818
|
+
},
|
|
819
|
+
{ timeout: 10000 }
|
|
820
|
+
);
|
|
821
|
+
|
|
822
|
+
// REGRESSION TEST: Core issue that was fixed
|
|
823
|
+
it(
|
|
824
|
+
'should properly cancel virtual commands and respect user SIGINT handlers (regression test)',
|
|
825
|
+
async () => {
|
|
826
|
+
// This test prevents regression of the core issue where:
|
|
827
|
+
// 1. Virtual commands (sleep) weren't being cancelled by SIGINT
|
|
828
|
+
// 2. SIGINT handler was interfering with user-defined handlers
|
|
829
|
+
// 3. Processes weren't outputting expected logs before interruption
|
|
830
|
+
|
|
831
|
+
// Test 1: Virtual command cancellation with proper exit codes
|
|
832
|
+
trace('SignalTest', 'Testing virtual command SIGINT cancellation...');
|
|
833
|
+
const child1 = spawn('node', ['js/examples/test-sleep.mjs'], {
|
|
834
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
835
|
+
detached: true,
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
let stdout1 = '';
|
|
839
|
+
child1.stdout.on('data', (data) => {
|
|
840
|
+
stdout1 += data.toString();
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
// Wait for command to start, then interrupt
|
|
844
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
845
|
+
child1.kill('SIGINT');
|
|
846
|
+
|
|
847
|
+
const exitCode1 = await new Promise((resolve) => {
|
|
848
|
+
child1.on('close', (code) => resolve(code));
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
// Should capture startup output and exit with SIGINT code
|
|
852
|
+
expect(stdout1).toContain('STARTING_SLEEP');
|
|
853
|
+
expect(stdout1).not.toContain('SLEEP_COMPLETED');
|
|
854
|
+
expect(exitCode1).toBe(130); // 128 + 2 (SIGINT)
|
|
855
|
+
trace('SignalTest', '✓ Virtual command properly cancelled with SIGINT');
|
|
856
|
+
|
|
857
|
+
// Test 2: User SIGINT handler cooperation
|
|
858
|
+
trace('SignalTest', 'Testing user SIGINT handler cooperation...');
|
|
859
|
+
const child2 = spawn(
|
|
860
|
+
'node',
|
|
861
|
+
[
|
|
862
|
+
'-e',
|
|
863
|
+
`
|
|
864
|
+
import { $ } from './js/src/$.mjs';
|
|
865
|
+
|
|
866
|
+
// Set up user's SIGINT handler AFTER importing our library
|
|
867
|
+
process.on('SIGINT', () => {
|
|
868
|
+
console.log('USER_HANDLER_EXECUTED');
|
|
869
|
+
process.exit(42);
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
console.log('PROCESS_READY');
|
|
873
|
+
|
|
874
|
+
// Run a virtual command that will be interrupted
|
|
875
|
+
try {
|
|
876
|
+
await \$\`sleep 5\`;
|
|
877
|
+
console.log('SLEEP_FINISHED');
|
|
878
|
+
} catch (err) {
|
|
879
|
+
console.log('SLEEP_ERROR');
|
|
880
|
+
}
|
|
881
|
+
`,
|
|
882
|
+
],
|
|
883
|
+
{
|
|
884
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
885
|
+
detached: true,
|
|
886
|
+
}
|
|
887
|
+
);
|
|
888
|
+
|
|
889
|
+
let stdout2 = '';
|
|
890
|
+
child2.stdout.on('data', (data) => {
|
|
891
|
+
stdout2 += data.toString();
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
// Wait for setup, then interrupt
|
|
895
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
896
|
+
child2.kill('SIGINT');
|
|
897
|
+
|
|
898
|
+
const exitCode2 = await new Promise((resolve) => {
|
|
899
|
+
child2.on('close', (code) => resolve(code));
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
// User's handler should take precedence
|
|
903
|
+
expect(stdout2).toContain('PROCESS_READY');
|
|
904
|
+
expect(stdout2).toContain('USER_HANDLER_EXECUTED');
|
|
905
|
+
expect(exitCode2).toBe(42); // User's custom exit code
|
|
906
|
+
trace('SignalTest', '✓ User SIGINT handler properly executed');
|
|
907
|
+
|
|
908
|
+
trace(
|
|
909
|
+
'SignalTest',
|
|
910
|
+
'🎉 Regression test passed - core issues remain fixed'
|
|
911
|
+
);
|
|
912
|
+
},
|
|
913
|
+
{ timeout: 15000 }
|
|
914
|
+
);
|
|
915
|
+
});
|