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,412 @@
|
|
|
1
|
+
import { $ } from '../src/$.mjs';
|
|
2
|
+
import { test, expect } from 'bun:test';
|
|
3
|
+
import './test-helper.mjs'; // Automatically sets up beforeEach/afterEach cleanup
|
|
4
|
+
|
|
5
|
+
test('path interpolation - basic unquoted path', () => {
|
|
6
|
+
const path = '/bin/echo';
|
|
7
|
+
const cmd = $({ mirror: false })`${path} hello`;
|
|
8
|
+
|
|
9
|
+
// With smart quoting, safe paths don't need quotes
|
|
10
|
+
expect(cmd.spec.command).toBe('/bin/echo hello');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('path interpolation - path with spaces gets quoted', () => {
|
|
14
|
+
const path = '/path with spaces/command';
|
|
15
|
+
const cmd = $({ mirror: false })`${path} hello`;
|
|
16
|
+
|
|
17
|
+
expect(cmd.spec.command).toBe("'/path with spaces/command' hello");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('path interpolation - path already wrapped in double quotes', () => {
|
|
21
|
+
const path = '"/path/to/command"';
|
|
22
|
+
const cmd = $({ mirror: false })`${path} hello`;
|
|
23
|
+
|
|
24
|
+
// Should preserve the double quotes by wrapping in single quotes
|
|
25
|
+
expect(cmd.spec.command).toBe('\'"/path/to/command"\' hello');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('path interpolation - path already wrapped in single quotes', () => {
|
|
29
|
+
const path = "'/path/to/command'";
|
|
30
|
+
const cmd = $({ mirror: false })`${path} hello`;
|
|
31
|
+
|
|
32
|
+
// With the fix, already-quoted paths should be used as-is when they don't contain internal quotes
|
|
33
|
+
expect(cmd.spec.command).toBe("'/path/to/command' hello");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('path interpolation - environment variable inheritance works', () => {
|
|
37
|
+
const originalEnv = process.env.TEST_PATH;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
process.env.TEST_PATH = '/usr/bin/echo';
|
|
41
|
+
const path = process.env.TEST_PATH;
|
|
42
|
+
const cmd = $({ mirror: false })`${path} hello`;
|
|
43
|
+
|
|
44
|
+
// Safe path doesn't need quotes
|
|
45
|
+
expect(cmd.spec.command).toBe('/usr/bin/echo hello');
|
|
46
|
+
} finally {
|
|
47
|
+
if (originalEnv !== undefined) {
|
|
48
|
+
process.env.TEST_PATH = originalEnv;
|
|
49
|
+
} else {
|
|
50
|
+
delete process.env.TEST_PATH;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('path interpolation - complex path scenarios', () => {
|
|
56
|
+
const testCases = [
|
|
57
|
+
{
|
|
58
|
+
name: 'Simple path',
|
|
59
|
+
path: '/Users/konard/.claude/local/claude',
|
|
60
|
+
expectedCommand: '/Users/konard/.claude/local/claude --version', // No quotes needed
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'Path with spaces',
|
|
64
|
+
path: '/Users/user name/.claude/local/claude',
|
|
65
|
+
expectedCommand: "'/Users/user name/.claude/local/claude' --version", // Quotes needed
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'Path with special characters',
|
|
69
|
+
path: '/Users/user-name_123/.claude/local/claude',
|
|
70
|
+
expectedCommand: '/Users/user-name_123/.claude/local/claude --version', // No quotes needed (dash and underscore are safe)
|
|
71
|
+
},
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
testCases.forEach(({ name, path, expectedCommand }) => {
|
|
75
|
+
const cmd = $({ mirror: false })`${path} --version`;
|
|
76
|
+
expect(cmd.spec.command).toBe(expectedCommand);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('path interpolation - stdin option with path works', () => {
|
|
81
|
+
const path = '/bin/cat';
|
|
82
|
+
const cmd = $({ stdin: 'test input\n', mirror: false })`${path}`;
|
|
83
|
+
|
|
84
|
+
expect(cmd.spec.command).toBe('/bin/cat'); // Safe path, no quotes needed
|
|
85
|
+
expect(cmd.options.stdin).toBe('test input\n');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('path interpolation - command building works correctly', () => {
|
|
89
|
+
const nonExistentPath = '/nonexistent/command';
|
|
90
|
+
const cmd = $({ mirror: false })`${nonExistentPath} --version`;
|
|
91
|
+
|
|
92
|
+
// Verify the command is built correctly (safe path, no quotes)
|
|
93
|
+
expect(cmd.spec.command).toBe('/nonexistent/command --version');
|
|
94
|
+
expect(cmd.spec.mode).toBe('shell');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('path interpolation - fixed escaping for simple pre-quoted paths', () => {
|
|
98
|
+
// This test verifies the fix for excessive escaping of pre-quoted paths
|
|
99
|
+
// The issue was that paths like "'/path/to/command'" would get double-escaped
|
|
100
|
+
|
|
101
|
+
const preQuotedPath = "'/path/to/command'"; // Already has single quotes
|
|
102
|
+
const cmd = $({ mirror: false })`${preQuotedPath} --version`;
|
|
103
|
+
|
|
104
|
+
// Fixed behavior: no excessive escaping for simple pre-quoted paths
|
|
105
|
+
const generated = cmd.spec.command;
|
|
106
|
+
expect(generated).not.toContain("\\'"); // Should NOT contain escaped quotes
|
|
107
|
+
expect(generated).toBe("'/path/to/command' --version"); // Should use path as-is
|
|
108
|
+
|
|
109
|
+
// The generated command should be valid shell syntax
|
|
110
|
+
expect(generated).toMatch(/^'.*' --version$/);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('path interpolation - environment variable scenario from GitHub issue', () => {
|
|
114
|
+
const originalEnv = process.env.CLAUDE_PATH;
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
// Simulate the exact scenario from the GitHub issue
|
|
118
|
+
process.env.CLAUDE_PATH = '/Users/konard/.claude/local/claude';
|
|
119
|
+
const claude =
|
|
120
|
+
process.env.CLAUDE_PATH || '/Users/konard/.claude/local/claude';
|
|
121
|
+
|
|
122
|
+
const cmd = $({
|
|
123
|
+
stdin: 'hi\n',
|
|
124
|
+
mirror: false,
|
|
125
|
+
})`${claude} --output-format stream-json --verbose --model sonnet`;
|
|
126
|
+
|
|
127
|
+
// Safe path, no quotes needed
|
|
128
|
+
expect(cmd.spec.command).toBe(
|
|
129
|
+
'/Users/konard/.claude/local/claude --output-format stream-json --verbose --model sonnet'
|
|
130
|
+
);
|
|
131
|
+
expect(cmd.options.stdin).toBe('hi\n');
|
|
132
|
+
expect(cmd.options.mirror).toBe(false);
|
|
133
|
+
|
|
134
|
+
// The command should be properly formatted for shell execution
|
|
135
|
+
expect(cmd.spec.mode).toBe('shell');
|
|
136
|
+
} finally {
|
|
137
|
+
if (originalEnv !== undefined) {
|
|
138
|
+
process.env.CLAUDE_PATH = originalEnv;
|
|
139
|
+
} else {
|
|
140
|
+
delete process.env.CLAUDE_PATH;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('path interpolation - improved handling of pre-quoted paths', () => {
|
|
146
|
+
// Test that the improved quoting logic handles pre-quoted paths better
|
|
147
|
+
const preQuotedPath = "'/path/to/claude'"; // Already has single quotes
|
|
148
|
+
const cmd = $({ mirror: false })`${preQuotedPath} --version`;
|
|
149
|
+
|
|
150
|
+
// With the fix, already-quoted paths should be used as-is when they don't contain internal quotes
|
|
151
|
+
expect(cmd.spec.command).toBe("'/path/to/claude' --version");
|
|
152
|
+
|
|
153
|
+
// Should not contain excessive escaping
|
|
154
|
+
expect(cmd.spec.command).not.toContain("\\'");
|
|
155
|
+
expect(cmd.spec.mode).toBe('shell');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('path interpolation - handles complex quoting edge cases', () => {
|
|
159
|
+
// Test various edge cases for the improved quoting logic
|
|
160
|
+
|
|
161
|
+
// Case 1: Path with single quotes inside (should still escape properly)
|
|
162
|
+
const pathWithInternalQuotes = "'/path/with'quotes/command'";
|
|
163
|
+
const cmd1 = $({ mirror: false })`${pathWithInternalQuotes} --version`;
|
|
164
|
+
// This should still use escaping because it has internal quotes
|
|
165
|
+
expect(cmd1.spec.command).toContain("\\'");
|
|
166
|
+
|
|
167
|
+
// Case 2: Empty quotes should be handled
|
|
168
|
+
const emptyQuoted = "''";
|
|
169
|
+
const cmd2 = $({ mirror: false })`echo ${emptyQuoted}`;
|
|
170
|
+
expect(cmd2.spec.command).toBe("echo ''");
|
|
171
|
+
|
|
172
|
+
// Case 3: Just quotes with no content
|
|
173
|
+
const justQuotes = "'";
|
|
174
|
+
const cmd3 = $({ mirror: false })`echo ${justQuotes}`;
|
|
175
|
+
expect(cmd3.spec.command).toBe("echo ''\\'\'\'");
|
|
176
|
+
|
|
177
|
+
// Case 4: Double-quoted paths should be wrapped in single quotes
|
|
178
|
+
const doubleQuotedPath = '"/path/with spaces/command"';
|
|
179
|
+
const cmd4 = $({ mirror: false })`${doubleQuotedPath} --version`;
|
|
180
|
+
expect(cmd4.spec.command).toBe('\'"/path/with spaces/command"\' --version');
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Shell injection prevention tests
|
|
184
|
+
test('shell injection - command substitution attempt', () => {
|
|
185
|
+
const malicious = '$(rm -rf /)';
|
|
186
|
+
const cmd = $({ mirror: false })`echo ${malicious}`;
|
|
187
|
+
|
|
188
|
+
// Should be safely quoted to prevent execution
|
|
189
|
+
expect(cmd.spec.command).toBe("echo '$(rm -rf /)'");
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test('shell injection - backtick command substitution', () => {
|
|
193
|
+
const malicious = '`cat /etc/passwd`';
|
|
194
|
+
const cmd = $({ mirror: false })`echo ${malicious}`;
|
|
195
|
+
|
|
196
|
+
// Should be safely quoted
|
|
197
|
+
expect(cmd.spec.command).toBe("echo '`cat /etc/passwd`'");
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test('shell injection - semicolon command chaining', () => {
|
|
201
|
+
const malicious = 'test; rm -rf /';
|
|
202
|
+
const cmd = $({ mirror: false })`echo ${malicious}`;
|
|
203
|
+
|
|
204
|
+
// Should be safely quoted to prevent command chaining
|
|
205
|
+
expect(cmd.spec.command).toBe("echo 'test; rm -rf /'");
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test('shell injection - pipe attempt', () => {
|
|
209
|
+
const malicious = 'test | cat /etc/passwd';
|
|
210
|
+
const cmd = $({ mirror: false })`echo ${malicious}`;
|
|
211
|
+
|
|
212
|
+
// Should be safely quoted to prevent piping
|
|
213
|
+
expect(cmd.spec.command).toBe("echo 'test | cat /etc/passwd'");
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('shell injection - AND operator attempt', () => {
|
|
217
|
+
const malicious = 'test && malicious_command';
|
|
218
|
+
const cmd = $({ mirror: false })`echo ${malicious}`;
|
|
219
|
+
|
|
220
|
+
// Should be safely quoted
|
|
221
|
+
expect(cmd.spec.command).toBe("echo 'test && malicious_command'");
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test('shell injection - OR operator attempt', () => {
|
|
225
|
+
const malicious = 'test || malicious_command';
|
|
226
|
+
const cmd = $({ mirror: false })`echo ${malicious}`;
|
|
227
|
+
|
|
228
|
+
// Should be safely quoted
|
|
229
|
+
expect(cmd.spec.command).toBe("echo 'test || malicious_command'");
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test('shell injection - background process attempt', () => {
|
|
233
|
+
const malicious = 'test & malicious_command';
|
|
234
|
+
const cmd = $({ mirror: false })`echo ${malicious}`;
|
|
235
|
+
|
|
236
|
+
// Should be safely quoted
|
|
237
|
+
expect(cmd.spec.command).toBe("echo 'test & malicious_command'");
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test('shell injection - variable expansion attempt', () => {
|
|
241
|
+
const malicious = '$PATH';
|
|
242
|
+
const cmd = $({ mirror: false })`echo ${malicious}`;
|
|
243
|
+
|
|
244
|
+
// Should be safely quoted to prevent variable expansion
|
|
245
|
+
expect(cmd.spec.command).toBe("echo '$PATH'");
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test('shell injection - glob expansion attempt', () => {
|
|
249
|
+
const malicious = '*.txt';
|
|
250
|
+
const cmd = $({ mirror: false })`echo ${malicious}`;
|
|
251
|
+
|
|
252
|
+
// Should be safely quoted to prevent glob expansion
|
|
253
|
+
expect(cmd.spec.command).toBe("echo '*.txt'");
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test('shell injection - redirect attempt', () => {
|
|
257
|
+
const malicious = '> /etc/passwd';
|
|
258
|
+
const cmd = $({ mirror: false })`echo test ${malicious}`;
|
|
259
|
+
|
|
260
|
+
// Should be safely quoted to prevent redirection
|
|
261
|
+
expect(cmd.spec.command).toBe("echo test '> /etc/passwd'");
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test('shell injection - newline injection', () => {
|
|
265
|
+
const malicious = 'test\nmalicious_command';
|
|
266
|
+
const cmd = $({ mirror: false })`echo ${malicious}`;
|
|
267
|
+
|
|
268
|
+
// Should be safely quoted with newline preserved
|
|
269
|
+
expect(cmd.spec.command).toBe("echo 'test\nmalicious_command'");
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test('shell injection - complex injection attempt', () => {
|
|
273
|
+
const malicious = '$(echo "pwned" > /tmp/pwned.txt)';
|
|
274
|
+
const cmd = $({ mirror: false })`ls ${malicious}`;
|
|
275
|
+
|
|
276
|
+
// Should be safely quoted
|
|
277
|
+
expect(cmd.spec.command).toBe('ls \'$(echo "pwned" > /tmp/pwned.txt)\'');
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test('safe strings - no unnecessary quoting', () => {
|
|
281
|
+
const testCases = [
|
|
282
|
+
{ input: 'hello', expected: 'echo hello' },
|
|
283
|
+
{ input: 'test123', expected: 'echo test123' },
|
|
284
|
+
{ input: '/usr/bin/node', expected: 'echo /usr/bin/node' },
|
|
285
|
+
{ input: 'file.txt', expected: 'echo file.txt' },
|
|
286
|
+
{ input: 'user@host.com', expected: 'echo user@host.com' },
|
|
287
|
+
{ input: 'key=value', expected: 'echo key=value' },
|
|
288
|
+
{ input: 'path/to/file', expected: 'echo path/to/file' },
|
|
289
|
+
{ input: 'v1.2.3', expected: 'echo v1.2.3' },
|
|
290
|
+
];
|
|
291
|
+
|
|
292
|
+
testCases.forEach(({ input, expected }) => {
|
|
293
|
+
const cmd = $({ mirror: false })`echo ${input}`;
|
|
294
|
+
expect(cmd.spec.command).toBe(expected);
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test('double-quoting prevention - user quotes with spaces', () => {
|
|
299
|
+
// User quotes a path that actually needs quotes (has spaces)
|
|
300
|
+
const pathWithSpaces = '/path with spaces/cmd';
|
|
301
|
+
|
|
302
|
+
// User provides single quotes
|
|
303
|
+
const singleQuoted = `'${pathWithSpaces}'`;
|
|
304
|
+
const cmd1 = $({ mirror: false })`${singleQuoted} --test`;
|
|
305
|
+
expect(cmd1.spec.command).toBe("'/path with spaces/cmd' --test");
|
|
306
|
+
|
|
307
|
+
// User provides double quotes
|
|
308
|
+
const doubleQuoted = `"${pathWithSpaces}"`;
|
|
309
|
+
const cmd2 = $({ mirror: false })`${doubleQuoted} --test`;
|
|
310
|
+
expect(cmd2.spec.command).toBe('\'"/path with spaces/cmd"\' --test');
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test('double-quoting prevention - user quotes with special chars', () => {
|
|
314
|
+
// User quotes a string with special characters
|
|
315
|
+
const dangerous = 'test; echo INJECTED';
|
|
316
|
+
|
|
317
|
+
// User provides single quotes
|
|
318
|
+
const singleQuoted = `'${dangerous}'`;
|
|
319
|
+
const cmd1 = $({ mirror: false })`echo ${singleQuoted}`;
|
|
320
|
+
expect(cmd1.spec.command).toBe("echo 'test; echo INJECTED'");
|
|
321
|
+
|
|
322
|
+
// User provides double quotes
|
|
323
|
+
const doubleQuoted = `"${dangerous}"`;
|
|
324
|
+
const cmd2 = $({ mirror: false })`echo ${doubleQuoted}`;
|
|
325
|
+
expect(cmd2.spec.command).toBe('echo \'"test; echo INJECTED"\'');
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test('double-quoting prevention - user unnecessarily quotes safe strings', () => {
|
|
329
|
+
// User quotes a safe string that doesn't need quotes
|
|
330
|
+
const safe = 'hello';
|
|
331
|
+
|
|
332
|
+
// User provides single quotes (unnecessary)
|
|
333
|
+
const singleQuoted = `'${safe}'`;
|
|
334
|
+
const cmd1 = $({ mirror: false })`echo ${singleQuoted}`;
|
|
335
|
+
expect(cmd1.spec.command).toBe("echo 'hello'");
|
|
336
|
+
|
|
337
|
+
// User provides double quotes (unnecessary)
|
|
338
|
+
const doubleQuoted = `"${safe}"`;
|
|
339
|
+
const cmd2 = $({ mirror: false })`echo ${doubleQuoted}`;
|
|
340
|
+
expect(cmd2.spec.command).toBe('echo \'"hello"\'');
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test('double-quoting prevention - mixed scenarios', () => {
|
|
344
|
+
const testCases = [
|
|
345
|
+
{
|
|
346
|
+
desc: 'Already single-quoted safe string',
|
|
347
|
+
input: "'safe'",
|
|
348
|
+
expected: "echo 'safe'",
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
desc: 'Already double-quoted safe string',
|
|
352
|
+
input: '"safe"',
|
|
353
|
+
expected: 'echo \'"safe"\'',
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
desc: 'Already single-quoted dangerous string',
|
|
357
|
+
input: "'rm -rf /'",
|
|
358
|
+
expected: "echo 'rm -rf /'",
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
desc: 'Already double-quoted dangerous string',
|
|
362
|
+
input: '"rm -rf /"',
|
|
363
|
+
expected: 'echo \'"rm -rf /"\'',
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
desc: 'Single-quoted path with spaces',
|
|
367
|
+
input: "'/usr/local bin/app'",
|
|
368
|
+
expected: "echo '/usr/local bin/app'",
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
desc: 'Double-quoted path with spaces',
|
|
372
|
+
input: '"/usr/local bin/app"',
|
|
373
|
+
expected: 'echo \'"/usr/local bin/app"\'',
|
|
374
|
+
},
|
|
375
|
+
];
|
|
376
|
+
|
|
377
|
+
testCases.forEach(({ desc, input, expected }) => {
|
|
378
|
+
const cmd = $({ mirror: false })`echo ${input}`;
|
|
379
|
+
expect(cmd.spec.command).toBe(expected);
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
test('strings requiring quotes - proper quoting applied', () => {
|
|
384
|
+
const testCases = [
|
|
385
|
+
{ input: 'hello world', expected: "echo 'hello world'" },
|
|
386
|
+
{ input: 'test$var', expected: "echo 'test$var'" },
|
|
387
|
+
{ input: 'cmd;ls', expected: "echo 'cmd;ls'" },
|
|
388
|
+
{ input: 'a|b', expected: "echo 'a|b'" },
|
|
389
|
+
{ input: 'a&b', expected: "echo 'a&b'" },
|
|
390
|
+
{ input: 'a>b', expected: "echo 'a>b'" },
|
|
391
|
+
{ input: 'a<b', expected: "echo 'a<b'" },
|
|
392
|
+
{ input: 'a*b', expected: "echo 'a*b'" },
|
|
393
|
+
{ input: 'a?b', expected: "echo 'a?b'" },
|
|
394
|
+
{ input: 'a[b]c', expected: "echo 'a[b]c'" },
|
|
395
|
+
{ input: 'a{b}c', expected: "echo 'a{b}c'" },
|
|
396
|
+
{ input: 'a(b)c', expected: "echo 'a(b)c'" },
|
|
397
|
+
{ input: 'a!b', expected: "echo 'a!b'" },
|
|
398
|
+
{ input: 'a#b', expected: "echo 'a#b'" },
|
|
399
|
+
{ input: 'a%b', expected: "echo 'a%b'" },
|
|
400
|
+
{ input: 'a^b', expected: "echo 'a^b'" },
|
|
401
|
+
{ input: 'a~b', expected: "echo 'a~b'" },
|
|
402
|
+
{ input: 'a`b', expected: "echo 'a`b'" },
|
|
403
|
+
{ input: "a'b", expected: "echo 'a'\\''b'" },
|
|
404
|
+
{ input: 'a"b', expected: "echo 'a\"b'" },
|
|
405
|
+
{ input: 'a\\b', expected: "echo 'a\\b'" },
|
|
406
|
+
];
|
|
407
|
+
|
|
408
|
+
testCases.forEach(({ input, expected }) => {
|
|
409
|
+
const cmd = $({ mirror: false })`echo ${input}`;
|
|
410
|
+
expect(cmd.spec.command).toBe(expected);
|
|
411
|
+
});
|
|
412
|
+
});
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { test, expect, describe, beforeEach, afterEach } from 'bun:test';
|
|
2
|
+
import './test-helper.mjs'; // Automatically sets up beforeEach/afterEach cleanup
|
|
3
|
+
import {
|
|
4
|
+
$,
|
|
5
|
+
register,
|
|
6
|
+
unregister,
|
|
7
|
+
enableVirtualCommands,
|
|
8
|
+
disableVirtualCommands,
|
|
9
|
+
} from '../src/$.mjs';
|
|
10
|
+
import { rmSync, existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
11
|
+
import { join } from 'path';
|
|
12
|
+
|
|
13
|
+
// Test directory for safe file operations
|
|
14
|
+
const TEST_DIR = 'test-pipe';
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
// Enable virtual commands for these tests
|
|
18
|
+
enableVirtualCommands();
|
|
19
|
+
|
|
20
|
+
// Create clean test directory
|
|
21
|
+
if (existsSync(TEST_DIR)) {
|
|
22
|
+
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
23
|
+
}
|
|
24
|
+
mkdirSync(TEST_DIR);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
// Clean up test directory
|
|
29
|
+
if (existsSync(TEST_DIR)) {
|
|
30
|
+
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('Programmatic .pipe() Method', () => {
|
|
35
|
+
describe('Basic Piping', () => {
|
|
36
|
+
test('should pipe between built-in commands', async () => {
|
|
37
|
+
register('add-prefix', async ({ args, stdin }) => {
|
|
38
|
+
const prefix = args[0] || 'PREFIX:';
|
|
39
|
+
return { stdout: `${prefix} ${stdin.trim()}\n`, code: 0 };
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const result = await $`echo "Hello World"`.pipe($`add-prefix "Piped:"`);
|
|
43
|
+
|
|
44
|
+
expect(result.code).toBe(0);
|
|
45
|
+
expect(result.stdout).toBe('Piped: Hello World\n');
|
|
46
|
+
|
|
47
|
+
// Cleanup
|
|
48
|
+
unregister('add-prefix');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('should pipe virtual commands', async () => {
|
|
52
|
+
register('double', async ({ args, stdin }) => {
|
|
53
|
+
const lines = stdin.split('\n').filter(Boolean);
|
|
54
|
+
const doubled = `${lines.map((line) => line + line).join('\n')}\n`;
|
|
55
|
+
return { stdout: doubled, code: 0 };
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
register('count-chars', async ({ args, stdin }) => {
|
|
59
|
+
const charCount = stdin.replace(/\n/g, '').length;
|
|
60
|
+
return { stdout: `${charCount}\n`, code: 0 };
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const result = await $`echo "hello"`.pipe($`double`).pipe($`count-chars`);
|
|
64
|
+
|
|
65
|
+
expect(result.code).toBe(0);
|
|
66
|
+
expect(result.stdout).toBe('10\n'); // "hellohello" = 10 chars
|
|
67
|
+
|
|
68
|
+
// Cleanup
|
|
69
|
+
unregister('double');
|
|
70
|
+
unregister('count-chars');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('should handle stdin properly in pipe chain', async () => {
|
|
74
|
+
register('prefix', async ({ args, stdin }) => {
|
|
75
|
+
const prefix = args[0] || 'PREFIX:';
|
|
76
|
+
const lines = stdin.split('\n').filter(Boolean);
|
|
77
|
+
const prefixed = `${lines.map((line) => `${prefix} ${line}`).join('\n')}\n`;
|
|
78
|
+
return { stdout: prefixed, code: 0 };
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const result = await $`echo "test"`.pipe($`prefix "[PIPED]"`);
|
|
82
|
+
|
|
83
|
+
expect(result.code).toBe(0);
|
|
84
|
+
expect(result.stdout).toBe('[PIPED] test\n');
|
|
85
|
+
|
|
86
|
+
// Cleanup
|
|
87
|
+
unregister('prefix');
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('Error Handling', () => {
|
|
92
|
+
test('should propagate errors from source command', async () => {
|
|
93
|
+
const result = await $`cat nonexistent-file.txt`.pipe(
|
|
94
|
+
$`echo "Should not reach here"`
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
expect(result.code).toBe(1);
|
|
98
|
+
// More flexible error message checking - different systems may format differently
|
|
99
|
+
expect(result.stderr).toMatch(
|
|
100
|
+
/No such file or directory|nonexistent-file\.txt|cannot access|Command failed with exit code 1/i
|
|
101
|
+
);
|
|
102
|
+
expect(result.stdout).toBe(''); // Destination should not execute
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('should handle errors in destination command', async () => {
|
|
106
|
+
register('fail', async ({ args, stdin }) => ({
|
|
107
|
+
stdout: '',
|
|
108
|
+
stderr: 'Virtual command failed',
|
|
109
|
+
code: 42,
|
|
110
|
+
}));
|
|
111
|
+
|
|
112
|
+
const result = await $`echo "hello"`.pipe($`fail`);
|
|
113
|
+
|
|
114
|
+
expect(result.code).toBe(42);
|
|
115
|
+
// More flexible error checking - the pipe implementation may wrap errors
|
|
116
|
+
expect(result.stderr).toMatch(
|
|
117
|
+
/Virtual command failed|Command failed with exit code 42/i
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
// Cleanup
|
|
121
|
+
unregister('fail');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('should handle exceptions in virtual commands', async () => {
|
|
125
|
+
register('throw-error', async ({ args, stdin }) => {
|
|
126
|
+
throw new Error('Something went wrong');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const result = await $`echo "hello"`.pipe($`throw-error`);
|
|
130
|
+
|
|
131
|
+
expect(result.code).toBe(1);
|
|
132
|
+
expect(result.stderr).toContain('Something went wrong');
|
|
133
|
+
|
|
134
|
+
// Cleanup
|
|
135
|
+
unregister('throw-error');
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('Complex Pipelines', () => {
|
|
140
|
+
test('should support multiple pipe operations', async () => {
|
|
141
|
+
register('uppercase', async ({ args, stdin }) => ({
|
|
142
|
+
stdout: stdin.toUpperCase(),
|
|
143
|
+
code: 0,
|
|
144
|
+
}));
|
|
145
|
+
|
|
146
|
+
register('reverse', async ({ args, stdin }) => {
|
|
147
|
+
const reversed = stdin.split('').reverse().join('');
|
|
148
|
+
return { stdout: reversed, code: 0 };
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
register('add-brackets', async ({ args, stdin }) => ({
|
|
152
|
+
stdout: `[${stdin.trim()}]\n`,
|
|
153
|
+
code: 0,
|
|
154
|
+
}));
|
|
155
|
+
|
|
156
|
+
const result = await $`echo "hello"`
|
|
157
|
+
.pipe($`uppercase`)
|
|
158
|
+
.pipe($`reverse`)
|
|
159
|
+
.pipe($`add-brackets`);
|
|
160
|
+
|
|
161
|
+
expect(result.code).toBe(0);
|
|
162
|
+
expect(result.stdout).toBe('[OLLEH]\n');
|
|
163
|
+
|
|
164
|
+
// Cleanup
|
|
165
|
+
unregister('uppercase');
|
|
166
|
+
unregister('reverse');
|
|
167
|
+
unregister('add-brackets');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test('should preserve stderr from all commands', async () => {
|
|
171
|
+
register('warn-and-pass', async ({ args, stdin }) => ({
|
|
172
|
+
stdout: stdin,
|
|
173
|
+
stderr: `Warning from ${args[0]}\n`,
|
|
174
|
+
code: 0,
|
|
175
|
+
}));
|
|
176
|
+
|
|
177
|
+
const result = await $`echo "data"`
|
|
178
|
+
.pipe($`warn-and-pass cmd1`)
|
|
179
|
+
.pipe($`warn-and-pass cmd2`);
|
|
180
|
+
|
|
181
|
+
expect(result.code).toBe(0);
|
|
182
|
+
expect(result.stdout).toBe('data\n');
|
|
183
|
+
expect(result.stderr).toContain('Warning from cmd1');
|
|
184
|
+
expect(result.stderr).toContain('Warning from cmd2');
|
|
185
|
+
|
|
186
|
+
// Cleanup
|
|
187
|
+
unregister('warn-and-pass');
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe('Mixed Command Types', () => {
|
|
192
|
+
test('should pipe from built-in to virtual command', async () => {
|
|
193
|
+
const testFile = join(TEST_DIR, 'pipe-test.txt');
|
|
194
|
+
writeFileSync(testFile, 'Line 1\nLine 2\nLine 3\n');
|
|
195
|
+
|
|
196
|
+
register('count-lines', async ({ args, stdin }) => {
|
|
197
|
+
const lines = stdin.split('\n').filter(Boolean);
|
|
198
|
+
return { stdout: `${lines.length}\n`, code: 0 };
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const result = await $`cat ${testFile}`.pipe($`count-lines`);
|
|
202
|
+
|
|
203
|
+
expect(result.code).toBe(0);
|
|
204
|
+
expect(result.stdout).toBe('3\n');
|
|
205
|
+
|
|
206
|
+
// Cleanup
|
|
207
|
+
unregister('count-lines');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test('should pipe from virtual to built-in command', async () => {
|
|
211
|
+
register('generate-sequence', async ({ args }) => {
|
|
212
|
+
const count = parseInt(args[0] || 3);
|
|
213
|
+
const sequence = `${Array.from({ length: count }, (_, i) => i + 1).join('\n')}\n`;
|
|
214
|
+
return { stdout: sequence, code: 0 };
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
register('capture-lines', async ({ args, stdin }) => {
|
|
218
|
+
const lines = stdin.split('\n').filter(Boolean);
|
|
219
|
+
return { stdout: `Got ${lines.length} lines\n`, code: 0 };
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const result = await $`generate-sequence 5`.pipe($`capture-lines`);
|
|
223
|
+
|
|
224
|
+
expect(result.code).toBe(0);
|
|
225
|
+
expect(result.stdout).toBe('Got 5 lines\n');
|
|
226
|
+
|
|
227
|
+
// Cleanup
|
|
228
|
+
unregister('generate-sequence');
|
|
229
|
+
unregister('capture-lines');
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
describe('Performance and Memory', () => {
|
|
234
|
+
test('should handle large data efficiently', async () => {
|
|
235
|
+
register('generate-large', async ({ args }) => {
|
|
236
|
+
const lines = parseInt(args[0] || 1000);
|
|
237
|
+
let output = '';
|
|
238
|
+
for (let i = 1; i <= lines; i++) {
|
|
239
|
+
output += `Line ${i}\n`;
|
|
240
|
+
}
|
|
241
|
+
return { stdout: output, code: 0 };
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
register('count-occurrences', async ({ args, stdin }) => {
|
|
245
|
+
const pattern = args[0] || 'Line';
|
|
246
|
+
const matches = (stdin.match(new RegExp(pattern, 'g')) || []).length;
|
|
247
|
+
return { stdout: `${matches}\n`, code: 0 };
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
const start = Date.now();
|
|
251
|
+
const result = await $`generate-large 100`.pipe(
|
|
252
|
+
$`count-occurrences Line`
|
|
253
|
+
);
|
|
254
|
+
const elapsed = Date.now() - start;
|
|
255
|
+
|
|
256
|
+
expect(result.code).toBe(0);
|
|
257
|
+
expect(result.stdout).toBe('100\n');
|
|
258
|
+
expect(elapsed).toBeLessThan(2000); // Should complete within 2 seconds
|
|
259
|
+
|
|
260
|
+
// Cleanup
|
|
261
|
+
unregister('generate-large');
|
|
262
|
+
unregister('count-occurrences');
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
describe('Compatibility with Shell Piping', () => {
|
|
267
|
+
test('should work alongside shell pipe syntax', async () => {
|
|
268
|
+
register('format-output', async ({ args, stdin }) => ({
|
|
269
|
+
stdout: `Formatted: ${stdin.trim()}\n`,
|
|
270
|
+
code: 0,
|
|
271
|
+
}));
|
|
272
|
+
|
|
273
|
+
register('simple-pipe', async ({ args, stdin }) => ({
|
|
274
|
+
stdout: `${stdin.trim()} processed\n`,
|
|
275
|
+
code: 0,
|
|
276
|
+
}));
|
|
277
|
+
|
|
278
|
+
// Test that programmatic .pipe() works after shell pipe operations
|
|
279
|
+
const result = await $`echo "hello"`
|
|
280
|
+
.pipe($`simple-pipe`)
|
|
281
|
+
.pipe($`format-output`);
|
|
282
|
+
|
|
283
|
+
expect(result.code).toBe(0);
|
|
284
|
+
expect(result.stdout).toBe('Formatted: hello processed\n');
|
|
285
|
+
|
|
286
|
+
// Cleanup
|
|
287
|
+
unregister('format-output');
|
|
288
|
+
unregister('simple-pipe');
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
});
|