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,266 @@
|
|
|
1
|
+
import { test, expect, describe, beforeEach } from 'bun:test';
|
|
2
|
+
import './test-helper.mjs';
|
|
3
|
+
import { $, raw, shell, disableVirtualCommands } from '../src/$.mjs';
|
|
4
|
+
|
|
5
|
+
// Disable virtual commands for consistent system command behavior
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
shell.errexit(false);
|
|
8
|
+
disableVirtualCommands();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe('raw() function - Disable auto-escape', () => {
|
|
12
|
+
describe('basic functionality', () => {
|
|
13
|
+
test('should create raw object with string', () => {
|
|
14
|
+
const result = raw('test command');
|
|
15
|
+
expect(result).toEqual({ raw: 'test command' });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('should convert numbers to string', () => {
|
|
19
|
+
expect(raw(123)).toEqual({ raw: '123' });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('should convert boolean to string', () => {
|
|
23
|
+
expect(raw(true)).toEqual({ raw: 'true' });
|
|
24
|
+
expect(raw(false)).toEqual({ raw: 'false' });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('should handle empty string', () => {
|
|
28
|
+
expect(raw('')).toEqual({ raw: '' });
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('command execution with raw()', () => {
|
|
33
|
+
test('should execute simple raw command', async () => {
|
|
34
|
+
const cmd = raw('echo "hello world"');
|
|
35
|
+
const result = await $`${cmd}`;
|
|
36
|
+
expect(result.stdout.trim()).toBe('hello world');
|
|
37
|
+
expect(result.code).toBe(0);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('should execute command with && operator', async () => {
|
|
41
|
+
const cmd = raw('echo "step1" && echo "step2"');
|
|
42
|
+
const result = await $`${cmd}`;
|
|
43
|
+
expect(result.stdout).toContain('step1');
|
|
44
|
+
expect(result.stdout).toContain('step2');
|
|
45
|
+
expect(result.code).toBe(0);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('should execute command with || operator', async () => {
|
|
49
|
+
const cmd = raw('false || echo "fallback"');
|
|
50
|
+
const result = await $`${cmd}`;
|
|
51
|
+
expect(result.stdout.trim()).toBe('fallback');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('should execute command with pipe', async () => {
|
|
55
|
+
const cmd = raw('echo "hello world" | wc -w');
|
|
56
|
+
const result = await $`${cmd}`;
|
|
57
|
+
expect(result.stdout.trim()).toBe('2');
|
|
58
|
+
expect(result.code).toBe(0);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('should execute command with semicolon', async () => {
|
|
62
|
+
const cmd = raw('echo "first"; echo "second"');
|
|
63
|
+
const result = await $`${cmd}`;
|
|
64
|
+
expect(result.stdout).toContain('first');
|
|
65
|
+
expect(result.stdout).toContain('second');
|
|
66
|
+
expect(result.code).toBe(0);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('should handle command with subshell', async () => {
|
|
70
|
+
const cmd = raw('echo "outer: $(echo inner)"');
|
|
71
|
+
const result = await $`${cmd}`;
|
|
72
|
+
expect(result.stdout.trim()).toBe('outer: inner');
|
|
73
|
+
expect(result.code).toBe(0);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('should handle command with wildcards', async () => {
|
|
77
|
+
const cmd = raw('echo test-*.mjs | head -c 20');
|
|
78
|
+
const result = await $`${cmd}`;
|
|
79
|
+
// Should execute wildcard expansion
|
|
80
|
+
expect(result.code).toBe(0);
|
|
81
|
+
expect(result.stdout.length).toBeGreaterThan(0);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('combining raw() with safe interpolation', () => {
|
|
86
|
+
test('should mix raw command with safe variable', async () => {
|
|
87
|
+
const safeInput = 'test';
|
|
88
|
+
const result = await $`${raw('echo "prefix:"')} ${safeInput}`;
|
|
89
|
+
expect(result.stdout.trim()).toBe('prefix: test');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('should safely quote user input when mixed with raw', async () => {
|
|
93
|
+
const userInput = 'test; rm -rf /';
|
|
94
|
+
const result = await $`${raw('echo "User said:"')} ${userInput}`;
|
|
95
|
+
// User input should be safely quoted even when mixed with raw
|
|
96
|
+
expect(result.stdout).toContain('User said:');
|
|
97
|
+
expect(result.stdout).toContain('test; rm -rf /');
|
|
98
|
+
// Command should not execute the rm part
|
|
99
|
+
expect(result.code).toBe(0);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('should handle multiple raw() calls in one command', async () => {
|
|
103
|
+
const cmd1 = raw('echo "part1"');
|
|
104
|
+
const cmd2 = raw('&& echo "part2"');
|
|
105
|
+
const result = await $`${cmd1} ${cmd2}`;
|
|
106
|
+
expect(result.stdout).toContain('part1');
|
|
107
|
+
expect(result.stdout).toContain('part2');
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('comparison with normal interpolation', () => {
|
|
112
|
+
test('raw() executes shell operators, normal interpolation escapes them', async () => {
|
|
113
|
+
const cmdString = 'echo "test" && echo "test2"';
|
|
114
|
+
|
|
115
|
+
// With raw() - executes both commands
|
|
116
|
+
const rawResult = await $`${raw(cmdString)}`;
|
|
117
|
+
expect(rawResult.stdout).toContain('test');
|
|
118
|
+
expect(rawResult.stdout).toContain('test2');
|
|
119
|
+
|
|
120
|
+
// Without raw() - treats as literal string
|
|
121
|
+
const normalResult = await $`echo ${cmdString}`;
|
|
122
|
+
expect(normalResult.stdout.trim()).toBe(cmdString);
|
|
123
|
+
expect(normalResult.stdout).not.toMatch(/test\s+test2/); // Should be one line
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('raw() allows pipes, normal interpolation escapes them', async () => {
|
|
127
|
+
const cmdString = 'echo "hello world" | wc -w';
|
|
128
|
+
|
|
129
|
+
// With raw() - executes pipe
|
|
130
|
+
const rawResult = await $`${raw(cmdString)}`;
|
|
131
|
+
expect(rawResult.stdout.trim()).toBe('2');
|
|
132
|
+
|
|
133
|
+
// Without raw() - treats pipe as literal
|
|
134
|
+
const normalResult = await $`echo ${cmdString}`;
|
|
135
|
+
expect(normalResult.stdout).toContain('|');
|
|
136
|
+
expect(normalResult.stdout).toContain('wc');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('raw() allows command substitution, normal interpolation escapes it', async () => {
|
|
140
|
+
const cmdString = '$(echo test)';
|
|
141
|
+
|
|
142
|
+
// With raw() - executes substitution
|
|
143
|
+
const rawResult = await $`echo ${raw(cmdString)}`;
|
|
144
|
+
expect(rawResult.stdout.trim()).toBe('test');
|
|
145
|
+
|
|
146
|
+
// Without raw() - treats as literal
|
|
147
|
+
const normalResult = await $`echo ${cmdString}`;
|
|
148
|
+
expect(normalResult.stdout.trim()).toBe('$(echo test)');
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe('edge cases', () => {
|
|
153
|
+
test('should handle raw() with quotes in command', async () => {
|
|
154
|
+
const cmd = raw('echo "single\'quote" "double\\"quote"');
|
|
155
|
+
const result = await $`${cmd}`;
|
|
156
|
+
expect(result.code).toBe(0);
|
|
157
|
+
expect(result.stdout).toContain('single');
|
|
158
|
+
expect(result.stdout).toContain('quote');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('should handle raw() with environment variables', async () => {
|
|
162
|
+
const cmd = raw('echo $HOME');
|
|
163
|
+
const result = await $`${cmd}`;
|
|
164
|
+
expect(result.stdout.trim().length).toBeGreaterThan(0);
|
|
165
|
+
expect(result.stdout.trim()).not.toBe('$HOME');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('should handle raw() with newlines', async () => {
|
|
169
|
+
const cmd = raw('echo "line1"\necho "line2"');
|
|
170
|
+
const result = await $`${cmd}`;
|
|
171
|
+
expect(result.stdout).toContain('line1');
|
|
172
|
+
expect(result.stdout).toContain('line2');
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test('should handle raw() at different positions', async () => {
|
|
176
|
+
// At start
|
|
177
|
+
const result1 = await $`${raw('echo "start"')} end`;
|
|
178
|
+
expect(result1.stdout.trim()).toContain('start');
|
|
179
|
+
|
|
180
|
+
// In middle
|
|
181
|
+
const result2 = await $`echo start ${raw('&& echo "middle"')} end`;
|
|
182
|
+
expect(result2.stdout).toContain('middle');
|
|
183
|
+
|
|
184
|
+
// At end
|
|
185
|
+
const result3 = await $`echo start ${raw('&& echo "end"')}`;
|
|
186
|
+
expect(result3.stdout).toContain('end');
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe('practical use cases', () => {
|
|
191
|
+
test('configuration-based commands', async () => {
|
|
192
|
+
const config = {
|
|
193
|
+
buildCommand: raw('echo "Building..." && echo "Done"'),
|
|
194
|
+
testCommand: raw('echo "Testing..." && echo "Passed"'),
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const buildResult = await $`${config.buildCommand}`;
|
|
198
|
+
expect(buildResult.stdout).toContain('Building');
|
|
199
|
+
expect(buildResult.stdout).toContain('Done');
|
|
200
|
+
|
|
201
|
+
const testResult = await $`${config.testCommand}`;
|
|
202
|
+
expect(testResult.stdout).toContain('Testing');
|
|
203
|
+
expect(testResult.stdout).toContain('Passed');
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test('complex shell pipelines', async () => {
|
|
207
|
+
const pipeline = raw('seq 1 10 | head -n 3 | tail -n 1');
|
|
208
|
+
const result = await $`${pipeline}`;
|
|
209
|
+
expect(result.stdout.trim()).toBe('3');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test('conditional execution chains', async () => {
|
|
213
|
+
const successChain = raw('true && echo "success"');
|
|
214
|
+
const result1 = await $`${successChain}`;
|
|
215
|
+
expect(result1.stdout.trim()).toBe('success');
|
|
216
|
+
|
|
217
|
+
const failChain = raw('false && echo "should not print"');
|
|
218
|
+
const result2 = await $`${failChain}`;
|
|
219
|
+
expect(result2.stdout.trim()).toBe('');
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe('security demonstrations', () => {
|
|
224
|
+
test('normal interpolation prevents injection', async () => {
|
|
225
|
+
const malicious = '; rm -rf /tmp/test';
|
|
226
|
+
const result = await $`echo ${malicious}`;
|
|
227
|
+
|
|
228
|
+
// Should safely output the string, not execute rm
|
|
229
|
+
expect(result.stdout).toContain('; rm -rf /tmp/test');
|
|
230
|
+
expect(result.code).toBe(0);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test('raw() would execute injection (demonstration only)', async () => {
|
|
234
|
+
// We demonstrate the danger without actually running dangerous commands
|
|
235
|
+
const dangerous = raw(
|
|
236
|
+
'echo "safe" ; echo "This would be dangerous with rm -rf"'
|
|
237
|
+
);
|
|
238
|
+
const result = await $`${dangerous}`;
|
|
239
|
+
|
|
240
|
+
// Both parts execute because raw() disables escaping
|
|
241
|
+
expect(result.stdout).toContain('safe');
|
|
242
|
+
expect(result.stdout).toContain('dangerous');
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
describe('error handling', () => {
|
|
247
|
+
test('should handle failing commands in raw()', async () => {
|
|
248
|
+
const cmd = raw('false');
|
|
249
|
+
const result = await $`${cmd}`;
|
|
250
|
+
expect(result.code).toBe(1);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test('should handle non-existent commands in raw()', async () => {
|
|
254
|
+
const cmd = raw('nonexistent-command-12345');
|
|
255
|
+
const result = await $`${cmd}`;
|
|
256
|
+
expect(result.code).not.toBe(0);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test('should handle syntax errors in raw()', async () => {
|
|
260
|
+
// Invalid shell syntax
|
|
261
|
+
const cmd = raw('echo "unclosed quote');
|
|
262
|
+
const result = await $`${cmd}`;
|
|
263
|
+
expect(result.code).not.toBe(0);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
});
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import { test, expect, describe, beforeEach, afterEach } from 'bun:test';
|
|
2
|
+
import { beforeTestCleanup, afterTestCleanup } from './test-cleanup.mjs';
|
|
3
|
+
import {
|
|
4
|
+
$,
|
|
5
|
+
sh,
|
|
6
|
+
create,
|
|
7
|
+
shell,
|
|
8
|
+
set,
|
|
9
|
+
unset,
|
|
10
|
+
disableVirtualCommands,
|
|
11
|
+
} from '../src/$.mjs';
|
|
12
|
+
|
|
13
|
+
// Helper function to setup shell settings for README tests
|
|
14
|
+
function setupShellForReadme() {
|
|
15
|
+
shell.errexit(false);
|
|
16
|
+
shell.verbose(false);
|
|
17
|
+
shell.xtrace(false);
|
|
18
|
+
shell.pipefail(false);
|
|
19
|
+
shell.nounset(false);
|
|
20
|
+
// Disable virtual commands for these tests to ensure system command behavior
|
|
21
|
+
disableVirtualCommands();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe('README Examples and Use Cases', () => {
|
|
25
|
+
beforeEach(async () => {
|
|
26
|
+
await beforeTestCleanup();
|
|
27
|
+
setupShellForReadme();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterEach(async () => {
|
|
31
|
+
await afterTestCleanup();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('1. Classic Await Pattern', () => {
|
|
35
|
+
test('should work like README example: await $`ls -la`', async () => {
|
|
36
|
+
const result = await $`echo "hello world"`;
|
|
37
|
+
expect(result.stdout.trim()).toBe('hello world');
|
|
38
|
+
expect(result.code).toBe(0);
|
|
39
|
+
expect(typeof result.stdout).toBe('string');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('2. Async Iteration Pattern', () => {
|
|
44
|
+
test('should work like README example: for await streaming', async () => {
|
|
45
|
+
const chunks = [];
|
|
46
|
+
|
|
47
|
+
for await (const chunk of $`echo "line1"; echo "line2"`.stream()) {
|
|
48
|
+
if (chunk.type === 'stdout') {
|
|
49
|
+
chunks.push(chunk.data.toString());
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const output = chunks.join('');
|
|
54
|
+
expect(output).toContain('line1');
|
|
55
|
+
expect(output).toContain('line2');
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('3. EventEmitter Pattern', () => {
|
|
60
|
+
test('should work like README example: .on() events', async () => {
|
|
61
|
+
const events = [];
|
|
62
|
+
|
|
63
|
+
const result = await new Promise((resolve) => {
|
|
64
|
+
const cmd = $`sh -c "echo 'stdout'; echo 'stderr' >&2"`;
|
|
65
|
+
cmd
|
|
66
|
+
.on('data', (chunk) => {
|
|
67
|
+
if (chunk.type === 'stdout') {
|
|
68
|
+
events.push('stdout-data');
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
.on('stderr', (chunk) => events.push('stderr'))
|
|
72
|
+
.on('end', (result) => {
|
|
73
|
+
events.push('end');
|
|
74
|
+
resolve(result);
|
|
75
|
+
})
|
|
76
|
+
.on('exit', (code) => events.push(`exit-${code}`));
|
|
77
|
+
|
|
78
|
+
// Start the command
|
|
79
|
+
cmd.then().catch(() => {});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
expect(events).toContain('stdout-data');
|
|
83
|
+
expect(events).toContain('stderr');
|
|
84
|
+
expect(events).toContain('end');
|
|
85
|
+
expect(events).toContain('exit-0');
|
|
86
|
+
expect(result.code).toBe(0);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('4. Mixed Pattern', () => {
|
|
91
|
+
test('should work like README example: events + await', async () => {
|
|
92
|
+
const process = $`echo "streaming data"`;
|
|
93
|
+
const chunks = [];
|
|
94
|
+
|
|
95
|
+
// Handle real-time events
|
|
96
|
+
process.on('data', (chunk) => {
|
|
97
|
+
chunks.push(chunk);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Still get the final result
|
|
101
|
+
const result = await process;
|
|
102
|
+
|
|
103
|
+
expect(result.stdout.trim()).toBe('streaming data');
|
|
104
|
+
expect(chunks.length).toBeGreaterThan(0);
|
|
105
|
+
expect(chunks[0]).toHaveProperty('type');
|
|
106
|
+
expect(chunks[0]).toHaveProperty('data');
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('5. Shell Replacement (.sh → .mjs)', () => {
|
|
111
|
+
test('should work like README example: shell settings', async () => {
|
|
112
|
+
// Test the exact example from README
|
|
113
|
+
shell.errexit(true);
|
|
114
|
+
|
|
115
|
+
await $`mkdir -p /tmp/test-build`;
|
|
116
|
+
|
|
117
|
+
// This should work
|
|
118
|
+
const result1 = await $`echo "build success"`;
|
|
119
|
+
expect(result1.code).toBe(0);
|
|
120
|
+
|
|
121
|
+
// set +e equivalent: allow errors
|
|
122
|
+
shell.errexit(false);
|
|
123
|
+
const cleanup = await $`ls /nonexistent-dir-12345`; // Won't throw if fails
|
|
124
|
+
expect(cleanup.code).not.toBe(0); // Should fail but not throw
|
|
125
|
+
|
|
126
|
+
// set -e again for critical operations
|
|
127
|
+
shell.errexit(true);
|
|
128
|
+
await $`echo "critical operation"`;
|
|
129
|
+
|
|
130
|
+
// Other bash-like settings
|
|
131
|
+
shell.verbose(true);
|
|
132
|
+
shell.xtrace(true);
|
|
133
|
+
|
|
134
|
+
// Or use the bash-style API
|
|
135
|
+
set('e');
|
|
136
|
+
expect(shell.settings().errexit).toBe(true);
|
|
137
|
+
|
|
138
|
+
unset('e');
|
|
139
|
+
expect(shell.settings().errexit).toBe(false);
|
|
140
|
+
|
|
141
|
+
set('x');
|
|
142
|
+
expect(shell.settings().xtrace).toBe(true);
|
|
143
|
+
|
|
144
|
+
set('verbose');
|
|
145
|
+
expect(shell.settings().verbose).toBe(true);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('6. Default Behavior', () => {
|
|
150
|
+
test('should work like README example: stdout/stderr capture + mirroring', async () => {
|
|
151
|
+
// This command will:
|
|
152
|
+
// 1. Print "Hello" to your terminal (stdout→stdout)
|
|
153
|
+
// 2. Print "Error!" to your terminal (stderr→stderr)
|
|
154
|
+
// 3. Capture both outputs for programmatic access
|
|
155
|
+
const result = await $`sh -c "echo 'Hello'; echo 'Error!' >&2"`;
|
|
156
|
+
|
|
157
|
+
expect(result.stdout.trim()).toBe('Hello');
|
|
158
|
+
expect(result.stderr.trim()).toBe('Error!');
|
|
159
|
+
expect(result.code).toBe(0);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe('7. Options Override', () => {
|
|
164
|
+
test('should work like README example: sh() with options', async () => {
|
|
165
|
+
// Disable terminal output but still capture
|
|
166
|
+
const result = await sh('echo "silent"', { mirror: false });
|
|
167
|
+
expect(result.stdout.trim()).toBe('silent');
|
|
168
|
+
|
|
169
|
+
// Custom stdin input
|
|
170
|
+
const custom = await sh('cat', { stdin: 'custom input' });
|
|
171
|
+
expect(custom.stdout.trim()).toBe('custom input');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('should work like README example: create() with defaults', async () => {
|
|
175
|
+
// Create custom $ with different defaults
|
|
176
|
+
const quiet$ = create({ mirror: false });
|
|
177
|
+
const result = await quiet$`echo "silent"`;
|
|
178
|
+
expect(result.stdout.trim()).toBe('silent');
|
|
179
|
+
|
|
180
|
+
// Disable both mirroring and capturing for performance
|
|
181
|
+
const perfResult = await sh('echo "performance"', {
|
|
182
|
+
mirror: false,
|
|
183
|
+
capture: false,
|
|
184
|
+
});
|
|
185
|
+
expect(perfResult.stdout).toBeUndefined();
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe('8. Real-world: Session ID Extraction', () => {
|
|
190
|
+
test('should work like README example: JSON parsing from streaming', async () => {
|
|
191
|
+
let sessionId = null;
|
|
192
|
+
let logFile = null;
|
|
193
|
+
const logData = [];
|
|
194
|
+
|
|
195
|
+
// Simulate a command that outputs JSON with session_id
|
|
196
|
+
const cmd = $`sh -c 'echo "{\\"session_id\\":\\"test-123\\",\\"status\\":\\"started\\"}"; echo "{\\"data\\":\\"some log data\\"}"'`;
|
|
197
|
+
|
|
198
|
+
let chunkCount = 0;
|
|
199
|
+
for await (const chunk of cmd.stream()) {
|
|
200
|
+
chunkCount++;
|
|
201
|
+
// Handle both possible chunk formats
|
|
202
|
+
const isStdout = chunk.type === 'stdout';
|
|
203
|
+
const hasData = chunk.data !== undefined;
|
|
204
|
+
|
|
205
|
+
if (isStdout && hasData) {
|
|
206
|
+
const data = chunk.data.toString();
|
|
207
|
+
|
|
208
|
+
// Extract session ID from output
|
|
209
|
+
if (!sessionId && data.includes('session_id')) {
|
|
210
|
+
try {
|
|
211
|
+
// Split by }{ to handle concatenated JSON objects
|
|
212
|
+
const jsonStrings = data.replace(/\}\{/g, '}\n{').split('\n');
|
|
213
|
+
|
|
214
|
+
for (const jsonStr of jsonStrings) {
|
|
215
|
+
const trimmed = jsonStr.trim();
|
|
216
|
+
if (trimmed && trimmed.includes('session_id')) {
|
|
217
|
+
const parsed = JSON.parse(trimmed);
|
|
218
|
+
if (parsed.session_id) {
|
|
219
|
+
sessionId = parsed.session_id;
|
|
220
|
+
logFile = `${sessionId}.log`;
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
} catch (e) {
|
|
226
|
+
// Handle JSON parse errors silently
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Write to log data (simulating file writes)
|
|
231
|
+
if (sessionId) {
|
|
232
|
+
logData.push(data);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// If no chunks were received, try to get the result directly
|
|
238
|
+
if (chunkCount === 0) {
|
|
239
|
+
const result = await cmd;
|
|
240
|
+
if (result.stdout && result.stdout.includes('session_id')) {
|
|
241
|
+
const lines = result.stdout.split('\n');
|
|
242
|
+
for (const line of lines) {
|
|
243
|
+
if (line.trim() && line.includes('session_id')) {
|
|
244
|
+
try {
|
|
245
|
+
const parsed = JSON.parse(line);
|
|
246
|
+
if (parsed.session_id) {
|
|
247
|
+
sessionId = parsed.session_id;
|
|
248
|
+
logFile = `${sessionId}.log`;
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
} catch (e) {
|
|
252
|
+
// Handle JSON parse errors
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
expect(sessionId).toBe('test-123');
|
|
260
|
+
expect(logFile).toBe('test-123.log');
|
|
261
|
+
// Adjust expectation based on whether streaming worked
|
|
262
|
+
if (chunkCount > 0) {
|
|
263
|
+
expect(logData.length).toBeGreaterThan(0);
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
describe('9. Real-world: Progress Monitoring', () => {
|
|
269
|
+
test('should work like README example: progress parsing from stdout', async () => {
|
|
270
|
+
let progress = 0;
|
|
271
|
+
let completed = false;
|
|
272
|
+
|
|
273
|
+
const parseProgress = (output) => {
|
|
274
|
+
const match = output.match(/Progress: (\d+)%/);
|
|
275
|
+
return match ? parseInt(match[1]) : 0;
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const updateProgressBar = (prog) => {
|
|
279
|
+
progress = prog;
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// Simulate a download command with progress output
|
|
283
|
+
await new Promise((resolve) => {
|
|
284
|
+
const cmd = $`sh -c 'echo "Starting download"; echo "Progress: 25%"; echo "Progress: 50%"; echo "Progress: 100%"; echo "Done!"'`;
|
|
285
|
+
cmd
|
|
286
|
+
.on('stdout', (chunk) => {
|
|
287
|
+
const output = chunk.toString();
|
|
288
|
+
if (output.includes('Progress:')) {
|
|
289
|
+
const newProgress = parseProgress(output);
|
|
290
|
+
if (newProgress > 0) {
|
|
291
|
+
updateProgressBar(newProgress);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
})
|
|
295
|
+
.on('end', (result) => {
|
|
296
|
+
completed = true;
|
|
297
|
+
resolve(result);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// Start the command
|
|
301
|
+
cmd.then().catch(() => {});
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
expect(progress).toBeGreaterThan(0);
|
|
305
|
+
expect(completed).toBe(true);
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
describe('10. API Documentation Examples', () => {
|
|
310
|
+
test('should match ProcessRunner events from API docs', async () => {
|
|
311
|
+
const events = [];
|
|
312
|
+
|
|
313
|
+
const result = await new Promise((resolve) => {
|
|
314
|
+
const cmd = $`sh -c "echo 'stdout'; echo 'stderr' >&2; exit 0"`;
|
|
315
|
+
cmd
|
|
316
|
+
.on('data', (chunk) => events.push(`data-${chunk.type}`))
|
|
317
|
+
.on('stdout', (chunk) => events.push('stdout'))
|
|
318
|
+
.on('stderr', (chunk) => events.push('stderr'))
|
|
319
|
+
.on('end', (result) => {
|
|
320
|
+
events.push('end');
|
|
321
|
+
resolve(result);
|
|
322
|
+
})
|
|
323
|
+
.on('exit', (code) => events.push(`exit-${code}`));
|
|
324
|
+
|
|
325
|
+
// Start the command
|
|
326
|
+
cmd.then().catch(() => {});
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
expect(events).toContain('data-stdout');
|
|
330
|
+
expect(events).toContain('data-stderr');
|
|
331
|
+
expect(events).toContain('stdout');
|
|
332
|
+
expect(events).toContain('stderr');
|
|
333
|
+
expect(events).toContain('end');
|
|
334
|
+
expect(events).toContain('exit-0');
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
test('should match Result Object from API docs', async () => {
|
|
338
|
+
const result = await $`sh -c "echo 'output'; echo 'error' >&2; exit 0"`;
|
|
339
|
+
|
|
340
|
+
// Verify result object structure from API docs
|
|
341
|
+
expect(typeof result.code).toBe('number');
|
|
342
|
+
expect(typeof result.stdout).toBe('string');
|
|
343
|
+
expect(typeof result.stderr).toBe('string');
|
|
344
|
+
expect(typeof result.stdin).toBe('string');
|
|
345
|
+
expect(result.child).toBeDefined();
|
|
346
|
+
|
|
347
|
+
expect(result.code).toBe(0);
|
|
348
|
+
expect(result.stdout.trim()).toBe('output');
|
|
349
|
+
expect(result.stderr.trim()).toBe('error');
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test('should verify default options from API docs', async () => {
|
|
353
|
+
const cmd = $`echo test`;
|
|
354
|
+
|
|
355
|
+
// Verify default options structure from API docs
|
|
356
|
+
expect(cmd.options.mirror).toBe(true);
|
|
357
|
+
expect(cmd.options.capture).toBe(true);
|
|
358
|
+
expect(cmd.options.stdin).toBe('inherit');
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
describe('Smart Quoting & Security (from README)', () => {
|
|
363
|
+
test('safe strings are NOT quoted', () => {
|
|
364
|
+
const name = 'hello';
|
|
365
|
+
const cmd = '/usr/bin/node';
|
|
366
|
+
|
|
367
|
+
const testCmd1 = $({ mirror: false })`echo ${name}`;
|
|
368
|
+
expect(testCmd1.spec.command).toBe('echo hello');
|
|
369
|
+
|
|
370
|
+
const testCmd2 = $({ mirror: false })`${cmd} --version`;
|
|
371
|
+
expect(testCmd2.spec.command).toBe('/usr/bin/node --version');
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
test('dangerous strings are automatically quoted', () => {
|
|
375
|
+
const userInput = 'test; rm -rf /';
|
|
376
|
+
const pathWithSpaces = '/my path/file';
|
|
377
|
+
|
|
378
|
+
const testCmd1 = $({ mirror: false })`echo ${userInput}`;
|
|
379
|
+
expect(testCmd1.spec.command).toBe("echo 'test; rm -rf /'");
|
|
380
|
+
|
|
381
|
+
const testCmd2 = $({ mirror: false })`echo ${pathWithSpaces}`;
|
|
382
|
+
expect(testCmd2.spec.command).toBe("echo '/my path/file'");
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
test('user-provided quotes are preserved', () => {
|
|
386
|
+
const quotedPath = "'/path with spaces/file'";
|
|
387
|
+
const doubleQuoted = '"/path with spaces/file"';
|
|
388
|
+
|
|
389
|
+
const testCmd1 = $({ mirror: false })`cat ${quotedPath}`;
|
|
390
|
+
expect(testCmd1.spec.command).toBe("cat '/path with spaces/file'");
|
|
391
|
+
|
|
392
|
+
const testCmd2 = $({ mirror: false })`cat ${doubleQuoted}`;
|
|
393
|
+
expect(testCmd2.spec.command).toBe('cat \'"/path with spaces/file"\'');
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
test('shell injection attempts are neutralized', () => {
|
|
397
|
+
const dangerous = "'; rm -rf /; echo '";
|
|
398
|
+
const cmdSubstitution = '$(whoami)';
|
|
399
|
+
const varExpansion = '$HOME';
|
|
400
|
+
const complex = '`cat /etc/passwd`';
|
|
401
|
+
|
|
402
|
+
const testCmd1 = $({ mirror: false })`echo ${dangerous}`;
|
|
403
|
+
expect(testCmd1.spec.command).toContain('rm -rf');
|
|
404
|
+
expect(testCmd1.spec.command).toMatch(/^echo '/);
|
|
405
|
+
|
|
406
|
+
const testCmd2 = $({ mirror: false })`echo ${cmdSubstitution}`;
|
|
407
|
+
expect(testCmd2.spec.command).toBe("echo '$(whoami)'");
|
|
408
|
+
|
|
409
|
+
const testCmd3 = $({ mirror: false })`echo ${varExpansion}`;
|
|
410
|
+
expect(testCmd3.spec.command).toBe("echo '$HOME'");
|
|
411
|
+
|
|
412
|
+
const testCmd4 = $({ mirror: false })`echo ${complex}`;
|
|
413
|
+
expect(testCmd4.spec.command).toBe("echo '`cat /etc/passwd`'");
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
test('actual execution prevents injection', async () => {
|
|
417
|
+
const varExpansion = '$HOME';
|
|
418
|
+
const cmdSubstitution = '$(echo INJECTED)';
|
|
419
|
+
|
|
420
|
+
const result1 = await $`echo ${varExpansion}`;
|
|
421
|
+
expect(result1.stdout.trim()).toBe('$HOME'); // Literal, not expanded
|
|
422
|
+
|
|
423
|
+
const result2 = await $`echo ${cmdSubstitution}`;
|
|
424
|
+
expect(result2.stdout.trim()).toBe('$(echo INJECTED)'); // Literal, not executed
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
});
|