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,279 @@
|
|
|
1
|
+
import { test, expect, describe, beforeEach } from 'bun:test';
|
|
2
|
+
import { isWindows } from './test-helper.mjs'; // Automatically sets up beforeEach/afterEach cleanup
|
|
3
|
+
import { $, shell, set, unset } from '../src/$.mjs';
|
|
4
|
+
|
|
5
|
+
describe('Shell Settings (set -e / set +e equivalent)', () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
// Reset all shell settings before each test
|
|
8
|
+
shell.errexit(false);
|
|
9
|
+
shell.verbose(false);
|
|
10
|
+
shell.xtrace(false);
|
|
11
|
+
shell.pipefail(false);
|
|
12
|
+
shell.nounset(false);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe('Error Handling (set -e / set +e)', () => {
|
|
16
|
+
test('should continue execution by default (like bash without set -e)', async () => {
|
|
17
|
+
const result1 = await $`exit 1`;
|
|
18
|
+
expect(result1.code).toBe(1);
|
|
19
|
+
|
|
20
|
+
const result2 = await $`echo "continued"`;
|
|
21
|
+
expect(result2.code).toBe(0);
|
|
22
|
+
expect(result2.stdout.trim()).toBe('continued');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('should throw on error when errexit enabled (set -e)', async () => {
|
|
26
|
+
shell.errexit(true);
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
await $`exit 42`;
|
|
30
|
+
expect(true).toBe(false); // Should not reach here
|
|
31
|
+
} catch (error) {
|
|
32
|
+
expect(error.code).toBe(42);
|
|
33
|
+
expect(error.message).toContain('Command failed with exit code 42');
|
|
34
|
+
expect(error.result).toBeDefined();
|
|
35
|
+
expect(error.result.code).toBe(42);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('should stop throwing when errexit disabled (set +e)', async () => {
|
|
40
|
+
shell.errexit(true);
|
|
41
|
+
shell.errexit(false);
|
|
42
|
+
|
|
43
|
+
const result = await $`exit 1`;
|
|
44
|
+
expect(result.code).toBe(1);
|
|
45
|
+
// Should not throw
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('should allow mid-script changes (like bash)', async () => {
|
|
49
|
+
// Start without errexit
|
|
50
|
+
const result1 = await $`exit 1`;
|
|
51
|
+
expect(result1.code).toBe(1);
|
|
52
|
+
|
|
53
|
+
// Enable errexit
|
|
54
|
+
shell.errexit(true);
|
|
55
|
+
try {
|
|
56
|
+
await $`exit 2`;
|
|
57
|
+
expect(true).toBe(false); // Should not reach here
|
|
58
|
+
} catch (error) {
|
|
59
|
+
expect(error.code).toBe(2);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Disable errexit again
|
|
63
|
+
shell.errexit(false);
|
|
64
|
+
const result3 = await $`exit 3`;
|
|
65
|
+
expect(result3.code).toBe(3);
|
|
66
|
+
// Should not throw
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('Verbose Mode (set -v)', () => {
|
|
71
|
+
test('should not print commands by default', async () => {
|
|
72
|
+
// Capture console output
|
|
73
|
+
const originalLog = console.log;
|
|
74
|
+
const capturedLogs = [];
|
|
75
|
+
console.log = (...args) => capturedLogs.push(args.join(' '));
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
await $`echo "silent"`;
|
|
79
|
+
expect(capturedLogs).toHaveLength(0);
|
|
80
|
+
} finally {
|
|
81
|
+
console.log = originalLog;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('should print commands when verbose enabled', async () => {
|
|
86
|
+
// Ensure clean state before intercepting console.log
|
|
87
|
+
shell.errexit(false);
|
|
88
|
+
shell.verbose(false);
|
|
89
|
+
shell.xtrace(false);
|
|
90
|
+
shell.pipefail(false);
|
|
91
|
+
shell.nounset(false);
|
|
92
|
+
|
|
93
|
+
const originalLog = console.log;
|
|
94
|
+
const capturedLogs = [];
|
|
95
|
+
console.log = (...args) => capturedLogs.push(args.join(' '));
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
shell.verbose(true);
|
|
99
|
+
await $`echo "verbose test"`;
|
|
100
|
+
|
|
101
|
+
expect(capturedLogs.length).toBeGreaterThan(0);
|
|
102
|
+
expect(
|
|
103
|
+
capturedLogs.some((log) => log.includes('echo "verbose test"'))
|
|
104
|
+
).toBe(true);
|
|
105
|
+
} finally {
|
|
106
|
+
console.log = originalLog;
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('Trace Mode (set -x)', () => {
|
|
112
|
+
test('should not trace commands by default', async () => {
|
|
113
|
+
const originalLog = console.log;
|
|
114
|
+
const capturedLogs = [];
|
|
115
|
+
console.log = (...args) => capturedLogs.push(args.join(' '));
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
await $`echo "no trace"`;
|
|
119
|
+
expect(capturedLogs).toHaveLength(0);
|
|
120
|
+
} finally {
|
|
121
|
+
console.log = originalLog;
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('should trace commands when xtrace enabled', async () => {
|
|
126
|
+
// Ensure clean state before intercepting console.log
|
|
127
|
+
shell.errexit(false);
|
|
128
|
+
shell.verbose(false);
|
|
129
|
+
shell.xtrace(false);
|
|
130
|
+
shell.pipefail(false);
|
|
131
|
+
shell.nounset(false);
|
|
132
|
+
|
|
133
|
+
const originalLog = console.log;
|
|
134
|
+
const capturedLogs = [];
|
|
135
|
+
console.log = (...args) => capturedLogs.push(args.join(' '));
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
shell.xtrace(true);
|
|
139
|
+
await $`echo "trace test"`;
|
|
140
|
+
|
|
141
|
+
expect(capturedLogs.length).toBeGreaterThan(0);
|
|
142
|
+
expect(capturedLogs.some((log) => log.startsWith('+ '))).toBe(true);
|
|
143
|
+
expect(
|
|
144
|
+
capturedLogs.some((log) => log.includes('echo "trace test"'))
|
|
145
|
+
).toBe(true);
|
|
146
|
+
} finally {
|
|
147
|
+
console.log = originalLog;
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe('Settings API', () => {
|
|
153
|
+
test('should allow setting options with set() function', () => {
|
|
154
|
+
set('e');
|
|
155
|
+
expect(shell.settings().errexit).toBe(true);
|
|
156
|
+
|
|
157
|
+
set('v');
|
|
158
|
+
expect(shell.settings().verbose).toBe(true);
|
|
159
|
+
|
|
160
|
+
set('x');
|
|
161
|
+
expect(shell.settings().xtrace).toBe(true);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('should allow unsetting options with unset() function', () => {
|
|
165
|
+
shell.errexit(true);
|
|
166
|
+
shell.verbose(true);
|
|
167
|
+
|
|
168
|
+
unset('e');
|
|
169
|
+
expect(shell.settings().errexit).toBe(false);
|
|
170
|
+
|
|
171
|
+
unset('v');
|
|
172
|
+
expect(shell.settings().verbose).toBe(false);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test('should support long option names', () => {
|
|
176
|
+
set('errexit');
|
|
177
|
+
expect(shell.settings().errexit).toBe(true);
|
|
178
|
+
|
|
179
|
+
set('verbose');
|
|
180
|
+
expect(shell.settings().verbose).toBe(true);
|
|
181
|
+
|
|
182
|
+
unset('errexit');
|
|
183
|
+
expect(shell.settings().errexit).toBe(false);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test('should return current settings', () => {
|
|
187
|
+
shell.errexit(true);
|
|
188
|
+
shell.verbose(true);
|
|
189
|
+
|
|
190
|
+
const settings = shell.settings();
|
|
191
|
+
expect(settings.errexit).toBe(true);
|
|
192
|
+
expect(settings.verbose).toBe(true);
|
|
193
|
+
expect(settings.xtrace).toBe(false);
|
|
194
|
+
expect(settings.pipefail).toBe(false);
|
|
195
|
+
expect(settings.nounset).toBe(false);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe('Shell Replacement Benefits', () => {
|
|
200
|
+
test('should provide better error objects than bash', async () => {
|
|
201
|
+
shell.errexit(true);
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
await $`sh -c "echo 'stdout'; echo 'stderr' >&2; exit 5"`;
|
|
205
|
+
expect(true).toBe(false);
|
|
206
|
+
} catch (error) {
|
|
207
|
+
expect(error.code).toBe(5);
|
|
208
|
+
expect(error.stdout).toContain('stdout');
|
|
209
|
+
expect(error.stderr).toContain('stderr');
|
|
210
|
+
expect(error.result).toBeDefined();
|
|
211
|
+
expect(error.result.code).toBe(5);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Skip on Windows - shell command execution differs
|
|
216
|
+
test.skipIf(isWindows)(
|
|
217
|
+
'should allow JavaScript control flow with shell semantics',
|
|
218
|
+
async () => {
|
|
219
|
+
const results = [];
|
|
220
|
+
|
|
221
|
+
// Test a list of commands with error handling
|
|
222
|
+
const commands = [
|
|
223
|
+
'echo "success1"',
|
|
224
|
+
'exit 1', // This will fail
|
|
225
|
+
'echo "success2"',
|
|
226
|
+
];
|
|
227
|
+
|
|
228
|
+
for (const cmd of commands) {
|
|
229
|
+
try {
|
|
230
|
+
shell.errexit(true);
|
|
231
|
+
const result = await $`sh -c ${cmd}`;
|
|
232
|
+
results.push({ cmd, success: true, output: result.stdout.trim() });
|
|
233
|
+
} catch (error) {
|
|
234
|
+
results.push({ cmd, success: false, code: error.code });
|
|
235
|
+
|
|
236
|
+
// Decide whether to continue or not
|
|
237
|
+
if (error.code === 1) {
|
|
238
|
+
shell.errexit(false); // Continue on this specific error
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
expect(results).toHaveLength(3);
|
|
244
|
+
expect(results[0].success).toBe(true);
|
|
245
|
+
expect(results[0].output).toBe('success1');
|
|
246
|
+
expect(results[1].success).toBe(false);
|
|
247
|
+
expect(results[1].code).toBe(1);
|
|
248
|
+
expect(results[2].success).toBe(true);
|
|
249
|
+
expect(results[2].output).toBe('success2');
|
|
250
|
+
}
|
|
251
|
+
);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
describe('Real-world Shell Script Pattern', () => {
|
|
255
|
+
test('should support common shell script patterns', async () => {
|
|
256
|
+
// Typical shell script pattern:
|
|
257
|
+
// set -e # exit on error
|
|
258
|
+
// optional commands with set +e
|
|
259
|
+
// set -e # back to strict mode
|
|
260
|
+
|
|
261
|
+
shell.errexit(true);
|
|
262
|
+
|
|
263
|
+
// Critical setup command
|
|
264
|
+
const setup = await $`echo "setup complete"`;
|
|
265
|
+
expect(setup.code).toBe(0);
|
|
266
|
+
|
|
267
|
+
// Optional command that might fail
|
|
268
|
+
shell.errexit(false);
|
|
269
|
+
const optional = await $`ls /nonexistent 2>/dev/null`;
|
|
270
|
+
expect(optional.code).not.toBe(0); // Should fail but not throw
|
|
271
|
+
|
|
272
|
+
// Back to strict mode for critical operations
|
|
273
|
+
shell.errexit(true);
|
|
274
|
+
const critical = await $`echo "critical operation"`;
|
|
275
|
+
expect(critical.code).toBe(0);
|
|
276
|
+
expect(critical.stdout.trim()).toBe('critical operation');
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
});
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { test, expect, describe } from 'bun:test';
|
|
2
|
+
import './test-helper.mjs'; // Automatically sets up beforeEach/afterEach cleanup
|
|
3
|
+
import { $ } from '../src/$.mjs';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { dirname, join } from 'path';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
|
|
10
|
+
// Platform detection - Windows handles signals differently than Unix
|
|
11
|
+
const isWindows = process.platform === 'win32';
|
|
12
|
+
|
|
13
|
+
// Skip on Windows - SIGINT handler testing requires Unix signal semantics
|
|
14
|
+
describe.skipIf(isWindows)('SIGINT Cleanup Tests (Isolated)', () => {
|
|
15
|
+
test('should properly manage SIGINT handlers', async () => {
|
|
16
|
+
// Run the test in a subprocess to avoid interfering with test runner
|
|
17
|
+
const scriptPath = join(__dirname, '../examples/sigint-handler-test.mjs');
|
|
18
|
+
const result = await $`node ${scriptPath}`;
|
|
19
|
+
|
|
20
|
+
// Parse results from output
|
|
21
|
+
const output = result.stdout;
|
|
22
|
+
const results = {};
|
|
23
|
+
|
|
24
|
+
// Extract RESULT lines
|
|
25
|
+
const resultLines = output
|
|
26
|
+
.split('\n')
|
|
27
|
+
.filter((line) => line.includes('RESULT:'));
|
|
28
|
+
resultLines.forEach((line) => {
|
|
29
|
+
const match = line.match(/RESULT: (\w+)=(.+)/);
|
|
30
|
+
if (match) {
|
|
31
|
+
results[match[1]] = match[2];
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Verify handler lifecycle
|
|
36
|
+
const initial = parseInt(results.initial_listeners);
|
|
37
|
+
const during = parseInt(results.during_listeners);
|
|
38
|
+
const after = parseInt(results.after_listeners);
|
|
39
|
+
|
|
40
|
+
expect(during).toBe(initial + 1); // Handler added
|
|
41
|
+
expect(after).toBe(initial); // Handler removed
|
|
42
|
+
|
|
43
|
+
// Verify concurrent commands share handler
|
|
44
|
+
const concurrent = parseInt(results.concurrent_listeners);
|
|
45
|
+
const afterConcurrent = parseInt(results.after_concurrent_listeners);
|
|
46
|
+
|
|
47
|
+
expect(concurrent).toBe(initial + 1); // Single handler for all
|
|
48
|
+
expect(afterConcurrent).toBe(initial); // Cleaned up
|
|
49
|
+
|
|
50
|
+
// Verify cleanup on error
|
|
51
|
+
const afterError = parseInt(results.after_error_listeners);
|
|
52
|
+
expect(afterError).toBe(initial);
|
|
53
|
+
|
|
54
|
+
// Verify cleanup on kill
|
|
55
|
+
const afterKill = parseInt(results.after_kill_listeners);
|
|
56
|
+
expect(afterKill).toBe(initial);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('should forward SIGINT to child processes', async () => {
|
|
60
|
+
// Run the forwarding test in subprocess
|
|
61
|
+
const scriptPath = join(
|
|
62
|
+
__dirname,
|
|
63
|
+
'../examples/sigint-forwarding-test.mjs'
|
|
64
|
+
);
|
|
65
|
+
const result = await $`node ${scriptPath}`;
|
|
66
|
+
|
|
67
|
+
// Parse results
|
|
68
|
+
const output = result.stdout;
|
|
69
|
+
const results = {};
|
|
70
|
+
|
|
71
|
+
const resultLines = output
|
|
72
|
+
.split('\n')
|
|
73
|
+
.filter((line) => line.includes('RESULT:'));
|
|
74
|
+
resultLines.forEach((line) => {
|
|
75
|
+
const match = line.match(/RESULT: (\w+)=(.+)/);
|
|
76
|
+
if (match) {
|
|
77
|
+
results[match[1]] = match[2];
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Verify SIGINT was received by parent
|
|
82
|
+
expect(results.sigint_received).toBe('true');
|
|
83
|
+
|
|
84
|
+
// The child process should have been forwarded the signal
|
|
85
|
+
// (exact behavior depends on implementation)
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('should cleanup all resources properly', async () => {
|
|
89
|
+
// Run comprehensive cleanup test
|
|
90
|
+
const scriptPath = join(
|
|
91
|
+
__dirname,
|
|
92
|
+
'../examples/cleanup-verification-test.mjs'
|
|
93
|
+
);
|
|
94
|
+
const result = await $`node ${scriptPath}`;
|
|
95
|
+
|
|
96
|
+
// Parse results
|
|
97
|
+
const output = result.stdout;
|
|
98
|
+
const results = {};
|
|
99
|
+
|
|
100
|
+
const resultLines = output
|
|
101
|
+
.split('\n')
|
|
102
|
+
.filter((line) => line.includes('RESULT:'));
|
|
103
|
+
resultLines.forEach((line) => {
|
|
104
|
+
const match = line.match(/RESULT: (\w+)=(.+)/);
|
|
105
|
+
if (match) {
|
|
106
|
+
results[match[1]] = match[2];
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Verify all cleanup scenarios
|
|
111
|
+
expect(results.virtual_finished).toBe('true');
|
|
112
|
+
expect(results.real_finished).toBe('true');
|
|
113
|
+
expect(results.error_finished).toBe('true');
|
|
114
|
+
expect(results.kill_finished).toBe('true');
|
|
115
|
+
expect(results.pipeline_output).toBe('test');
|
|
116
|
+
expect(results.event_listeners_size).toBe('0');
|
|
117
|
+
expect(results.concurrent_finished).toBe('true');
|
|
118
|
+
expect(results.not_awaited_finished).toBe('true');
|
|
119
|
+
expect(results.stream_iterator_finished).toBe('true');
|
|
120
|
+
expect(results.had_abort_controller).toBe('true');
|
|
121
|
+
expect(results.abort_controller_cleaned).toBe('true');
|
|
122
|
+
expect(results.final_sigint_handlers).toBe('0');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('should not interfere with user SIGINT handlers', async () => {
|
|
126
|
+
// This test verifies that after commands finish, user handlers work
|
|
127
|
+
await $`echo test`;
|
|
128
|
+
|
|
129
|
+
// Wait to ensure cleanup
|
|
130
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
131
|
+
|
|
132
|
+
// User's handler should work fine now
|
|
133
|
+
let userHandlerCalled = false;
|
|
134
|
+
const userHandler = () => {
|
|
135
|
+
userHandlerCalled = true;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
process.on('SIGINT', userHandler);
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
// This should NOT exit the process since we're not actually sending SIGINT
|
|
142
|
+
// We're just testing that we can add handlers
|
|
143
|
+
expect(process.listeners('SIGINT').includes(userHandler)).toBe(true);
|
|
144
|
+
|
|
145
|
+
// Clean up
|
|
146
|
+
process.removeListener('SIGINT', userHandler);
|
|
147
|
+
} finally {
|
|
148
|
+
process.removeListener('SIGINT', userHandler);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { test, expect, describe } from 'bun:test';
|
|
2
|
+
import './test-helper.mjs'; // Automatically sets up beforeEach/afterEach cleanup
|
|
3
|
+
import { $ } from '../src/$.mjs';
|
|
4
|
+
|
|
5
|
+
// Platform detection - Windows handles signals differently than Unix
|
|
6
|
+
const isWindows = process.platform === 'win32';
|
|
7
|
+
|
|
8
|
+
// Skip on Windows - SIGINT handler testing requires Unix signal semantics
|
|
9
|
+
describe.skipIf(isWindows)('SIGINT Handler Cleanup Tests', () => {
|
|
10
|
+
test('should remove SIGINT handler when all ProcessRunners finish', async () => {
|
|
11
|
+
// Check initial state
|
|
12
|
+
const initialListeners = process.listeners('SIGINT').length;
|
|
13
|
+
|
|
14
|
+
// Run a quick command that finishes immediately
|
|
15
|
+
const result = await $`echo hello`;
|
|
16
|
+
expect(result.code).toBe(0);
|
|
17
|
+
|
|
18
|
+
// After command finishes, SIGINT handler should be removed
|
|
19
|
+
const afterListeners = process.listeners('SIGINT').length;
|
|
20
|
+
expect(afterListeners).toBe(initialListeners);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('should not interfere with user SIGINT handlers after commands finish', async () => {
|
|
24
|
+
// Run a command and let it finish
|
|
25
|
+
await $`echo test`;
|
|
26
|
+
|
|
27
|
+
// Wait a bit to ensure all cleanup is complete
|
|
28
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
29
|
+
|
|
30
|
+
// Add user's SIGINT handler
|
|
31
|
+
let userHandlerCalled = false;
|
|
32
|
+
const userHandler = () => {
|
|
33
|
+
userHandlerCalled = true;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Test that we can add a handler without interference
|
|
37
|
+
process.on('SIGINT', userHandler);
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
// Verify the handler was added successfully
|
|
41
|
+
const listeners = process.listeners('SIGINT');
|
|
42
|
+
expect(listeners.includes(userHandler)).toBe(true);
|
|
43
|
+
|
|
44
|
+
// NOTE: We do NOT emit SIGINT here as it would kill the test runner
|
|
45
|
+
// The isolated test in sigint-cleanup-isolated.test.mjs tests actual SIGINT behavior
|
|
46
|
+
} finally {
|
|
47
|
+
// Cleanup
|
|
48
|
+
process.removeListener('SIGINT', userHandler);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('should maintain SIGINT handler while commands are active', async () => {
|
|
53
|
+
// Import forceCleanupAll to ensure clean state
|
|
54
|
+
const { forceCleanupAll } = await import('../src/$.mjs');
|
|
55
|
+
forceCleanupAll();
|
|
56
|
+
|
|
57
|
+
const initialListeners = process.listeners('SIGINT').length;
|
|
58
|
+
|
|
59
|
+
// Start a long-running command
|
|
60
|
+
const runner = $`sleep 2`;
|
|
61
|
+
const promise = runner.start();
|
|
62
|
+
|
|
63
|
+
// While command is running, handler should be installed
|
|
64
|
+
const duringListeners = process.listeners('SIGINT').length;
|
|
65
|
+
expect(duringListeners).toBe(initialListeners + 1);
|
|
66
|
+
|
|
67
|
+
// Kill the command
|
|
68
|
+
runner.kill();
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
await promise;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
// Expected error when process is killed with SIGTERM
|
|
74
|
+
expect(error.code).toBe(143);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// After command finishes, handler should be removed
|
|
78
|
+
const afterListeners = process.listeners('SIGINT').length;
|
|
79
|
+
|
|
80
|
+
// If listeners count doesn't match, force cleanup of command-stream handlers only
|
|
81
|
+
if (afterListeners !== initialListeners) {
|
|
82
|
+
const ourListeners = process.listeners('SIGINT').filter((l) => {
|
|
83
|
+
const str = l.toString();
|
|
84
|
+
return (
|
|
85
|
+
str.includes('activeProcessRunners') ||
|
|
86
|
+
str.includes('ProcessRunner') ||
|
|
87
|
+
str.includes('activeChildren')
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (ourListeners.length > 0) {
|
|
92
|
+
console.warn(
|
|
93
|
+
`Test left behind ${ourListeners.length} command-stream SIGINT handlers, forcing cleanup...`
|
|
94
|
+
);
|
|
95
|
+
ourListeners.forEach((listener) => {
|
|
96
|
+
process.removeListener('SIGINT', listener);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const finalListeners = process.listeners('SIGINT').length;
|
|
102
|
+
expect(finalListeners).toBe(initialListeners);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('should handle multiple concurrent ProcessRunners correctly', async () => {
|
|
106
|
+
const initialListeners = process.listeners('SIGINT').length;
|
|
107
|
+
|
|
108
|
+
// Start multiple commands
|
|
109
|
+
const promises = [$`echo one`, $`echo two`, $`echo three`];
|
|
110
|
+
|
|
111
|
+
// Wait for all to finish
|
|
112
|
+
await Promise.all(promises);
|
|
113
|
+
|
|
114
|
+
// Handler should be removed after all finish
|
|
115
|
+
const afterListeners = process.listeners('SIGINT').length;
|
|
116
|
+
expect(afterListeners).toBe(initialListeners);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { test, expect, describe, beforeEach, afterEach } from 'bun:test';
|
|
4
|
+
import { isWindows } from './test-helper.mjs'; // Automatically sets up beforeEach/afterEach cleanup
|
|
5
|
+
import { $, shell } from '../src/$.mjs';
|
|
6
|
+
|
|
7
|
+
describe('Start/Run Edge Cases and Advanced Usage', () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
shell.errexit(false);
|
|
10
|
+
shell.verbose(false);
|
|
11
|
+
shell.xtrace(false);
|
|
12
|
+
shell.pipefail(false);
|
|
13
|
+
shell.nounset(false);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
shell.errexit(false);
|
|
18
|
+
shell.verbose(false);
|
|
19
|
+
shell.xtrace(false);
|
|
20
|
+
shell.pipefail(false);
|
|
21
|
+
shell.nounset(false);
|
|
22
|
+
});
|
|
23
|
+
test('should handle complex option combinations', async () => {
|
|
24
|
+
const result = await $`echo "complex test"`.start({
|
|
25
|
+
capture: true,
|
|
26
|
+
mirror: false,
|
|
27
|
+
stdin: 'inherit',
|
|
28
|
+
mode: 'async',
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
expect(result.stdout).toBe('complex test\n');
|
|
32
|
+
expect(result.code).toBe(0);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Skip on Windows - uses 'ls -la /tmp' which is Unix-specific
|
|
36
|
+
test.skipIf(isWindows)(
|
|
37
|
+
'should work with real shell commands that produce large output',
|
|
38
|
+
async () => {
|
|
39
|
+
const result = await $`ls -la /tmp`.start({ capture: false });
|
|
40
|
+
|
|
41
|
+
expect(result.stdout).toBeUndefined();
|
|
42
|
+
expect(result.code).toBe(0);
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
test('should handle stderr with capture: false', async () => {
|
|
47
|
+
const result = await $`ls /nonexistent-path-12345`.start({
|
|
48
|
+
capture: false,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
expect(result.stdout).toBeUndefined();
|
|
52
|
+
expect(result.stderr).toBeUndefined();
|
|
53
|
+
expect(result.code).not.toBe(0); // ls should fail
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('should handle stderr with capture: true', async () => {
|
|
57
|
+
const result = await $`ls /nonexistent-path-98765`.start({ capture: true });
|
|
58
|
+
|
|
59
|
+
expect(result.stdout).toBe(''); // No stdout for failed ls
|
|
60
|
+
expect(typeof result.stderr).toBe('string');
|
|
61
|
+
expect(result.stderr.length).toBeGreaterThan(0);
|
|
62
|
+
expect(result.code).not.toBe(0);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('should handle multiple consecutive start() calls correctly', async () => {
|
|
66
|
+
const runner = $`echo "multiple calls"`;
|
|
67
|
+
|
|
68
|
+
// First call should work
|
|
69
|
+
const result1 = await runner.start({ capture: true });
|
|
70
|
+
expect(result1.stdout).toBe('multiple calls\n');
|
|
71
|
+
|
|
72
|
+
// Second call should return the same result (cached)
|
|
73
|
+
const result2 = await runner.start({ capture: false }); // Options ignored
|
|
74
|
+
expect(result2.stdout).toBe('multiple calls\n'); // Still captured
|
|
75
|
+
|
|
76
|
+
// Results should be the same object reference
|
|
77
|
+
expect(result1).toBe(result2);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('should handle mixed sync/async mode correctly', async () => {
|
|
81
|
+
const result1 = await $`echo "async mode"`.start({ mode: 'async' });
|
|
82
|
+
const result2 = $`echo "sync mode"`.start({ mode: 'sync' });
|
|
83
|
+
|
|
84
|
+
expect(result1.stdout).toBe('async mode\n');
|
|
85
|
+
expect(result2.stdout).toBe('sync mode\n');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('should preserve original behavior when no options passed', async () => {
|
|
89
|
+
const withStart = await $`echo "with start"`.start();
|
|
90
|
+
const withRun = await $`echo "with run"`.run();
|
|
91
|
+
const directAwait = await $`echo "direct await"`;
|
|
92
|
+
|
|
93
|
+
expect(withStart.stdout).toBe('with start\n');
|
|
94
|
+
expect(withRun.stdout).toBe('with run\n');
|
|
95
|
+
expect(directAwait.stdout).toBe('direct await\n');
|
|
96
|
+
|
|
97
|
+
// All should have same structure
|
|
98
|
+
expect(Object.keys(withStart)).toEqual(Object.keys(withRun));
|
|
99
|
+
expect(Object.keys(withRun)).toEqual(Object.keys(directAwait));
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('should work with piped commands', async () => {
|
|
103
|
+
const result = await $`echo "hello world"`
|
|
104
|
+
.pipe($`cat`)
|
|
105
|
+
.start({ mirror: false });
|
|
106
|
+
|
|
107
|
+
expect(result.stdout).toBe('hello world\n');
|
|
108
|
+
expect(result.code).toBe(0);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('should handle buffer stdin correctly with options', async () => {
|
|
112
|
+
const inputBuffer = Buffer.from('buffer test data');
|
|
113
|
+
const result = await $`cat`.start({
|
|
114
|
+
stdin: inputBuffer,
|
|
115
|
+
capture: true,
|
|
116
|
+
mirror: false,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Result might be a buffer or string depending on the command
|
|
120
|
+
const output =
|
|
121
|
+
typeof result.stdout === 'string'
|
|
122
|
+
? result.stdout
|
|
123
|
+
: result.stdout?.toString();
|
|
124
|
+
expect(output).toBe('buffer test data');
|
|
125
|
+
expect(result.code).toBe(0);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('should maintain performance with capture: false', async () => {
|
|
129
|
+
// This test verifies that when capture is false, we don't waste memory
|
|
130
|
+
const startTime = Date.now();
|
|
131
|
+
|
|
132
|
+
const result = await $`echo "performance test"`.start({ capture: false });
|
|
133
|
+
|
|
134
|
+
const endTime = Date.now();
|
|
135
|
+
const duration = endTime - startTime;
|
|
136
|
+
|
|
137
|
+
expect(result.stdout).toBeUndefined();
|
|
138
|
+
expect(result.code).toBe(0);
|
|
139
|
+
expect(duration).toBeLessThan(1000); // Should complete quickly
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('should handle empty string stdin', async () => {
|
|
143
|
+
const result = await $`cat`.start({
|
|
144
|
+
stdin: '',
|
|
145
|
+
capture: true,
|
|
146
|
+
mirror: false,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
expect(result.stdout).toBe('');
|
|
150
|
+
expect(result.code).toBe(0);
|
|
151
|
+
});
|
|
152
|
+
});
|