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.
Files changed (500) hide show
  1. package/js/examples/01-basic-streaming.mjs +14 -0
  2. package/js/examples/02-async-iterator.mjs +9 -0
  3. package/js/examples/03-file-and-console.mjs +16 -0
  4. package/js/examples/04-claude-jq-pipe.mjs +16 -0
  5. package/js/examples/CI-DEBUG-README.md +138 -0
  6. package/js/examples/README-examples.mjs +111 -0
  7. package/js/examples/README.md +345 -0
  8. package/js/examples/STREAMING_INTERFACES_SUMMARY.md +116 -0
  9. package/js/examples/add-test-timeouts.js +107 -0
  10. package/js/examples/ansi-default-preserved.mjs +11 -0
  11. package/js/examples/ansi-global-config.mjs +12 -0
  12. package/js/examples/ansi-reset-default.mjs +12 -0
  13. package/js/examples/ansi-strip-utils.mjs +12 -0
  14. package/js/examples/baseline-child-process.mjs +23 -0
  15. package/js/examples/baseline-claude-test.mjs +47 -0
  16. package/js/examples/baseline-working.mjs +34 -0
  17. package/js/examples/capture-mirror-comparison.mjs +23 -0
  18. package/js/examples/capture-mirror-default.mjs +11 -0
  19. package/js/examples/capture-mirror-performance.mjs +16 -0
  20. package/js/examples/capture-mirror-show-only.mjs +16 -0
  21. package/js/examples/capture-mirror-silent-processing.mjs +16 -0
  22. package/js/examples/ci-debug-baseline-vs-library.mjs +265 -0
  23. package/js/examples/ci-debug-es-module-loading.mjs +184 -0
  24. package/js/examples/ci-debug-signal-handling.mjs +225 -0
  25. package/js/examples/ci-debug-stdout-buffering.mjs +94 -0
  26. package/js/examples/ci-debug-test-timeouts.mjs +259 -0
  27. package/js/examples/claude-exact-file-output.mjs +20 -0
  28. package/js/examples/claude-exact-jq.mjs +13 -0
  29. package/js/examples/claude-exact-streaming.mjs +13 -0
  30. package/js/examples/claude-jq-pipeline.mjs +13 -0
  31. package/js/examples/claude-json-stream.mjs +39 -0
  32. package/js/examples/claude-streaming-basic.mjs +99 -0
  33. package/js/examples/claude-streaming-demo.mjs +126 -0
  34. package/js/examples/claude-streaming-final.mjs +20 -0
  35. package/js/examples/cleanup-verification-test.mjs +115 -0
  36. package/js/examples/colors-buffer-processing.mjs +14 -0
  37. package/js/examples/colors-default-preserved.mjs +15 -0
  38. package/js/examples/colors-per-command-config.mjs +15 -0
  39. package/js/examples/colors-strip-ansi.mjs +13 -0
  40. package/js/examples/commandstream-jq.mjs +29 -0
  41. package/js/examples/commandstream-working.mjs +23 -0
  42. package/js/examples/comprehensive-streams-demo.mjs +121 -0
  43. package/js/examples/ctrl-c-concurrent-processes.mjs +20 -0
  44. package/js/examples/ctrl-c-long-running-command.mjs +20 -0
  45. package/js/examples/ctrl-c-real-system-command.mjs +17 -0
  46. package/js/examples/ctrl-c-sleep-command.mjs +17 -0
  47. package/js/examples/ctrl-c-stdin-forwarding.mjs +20 -0
  48. package/js/examples/ctrl-c-virtual-command.mjs +17 -0
  49. package/js/examples/debug-already-started.mjs +20 -0
  50. package/js/examples/debug-ansi-processing.mjs +42 -0
  51. package/js/examples/debug-basic-streaming.mjs +44 -0
  52. package/js/examples/debug-buildshellcommand.mjs +82 -0
  53. package/js/examples/debug-child-process.mjs +43 -0
  54. package/js/examples/debug-child-state.mjs +55 -0
  55. package/js/examples/debug-chunking.mjs +38 -0
  56. package/js/examples/debug-command-parsing.mjs +61 -0
  57. package/js/examples/debug-complete-consolidation.mjs +97 -0
  58. package/js/examples/debug-echo-args.mjs +22 -0
  59. package/js/examples/debug-emit-timing.mjs +28 -0
  60. package/js/examples/debug-end-event.mjs +56 -0
  61. package/js/examples/debug-errexit.mjs +16 -0
  62. package/js/examples/debug-event-emission.mjs +40 -0
  63. package/js/examples/debug-event-timing.mjs +67 -0
  64. package/js/examples/debug-event-vs-result.mjs +30 -0
  65. package/js/examples/debug-exact-command.mjs +28 -0
  66. package/js/examples/debug-exact-test-scenario.mjs +44 -0
  67. package/js/examples/debug-execution-path.mjs +27 -0
  68. package/js/examples/debug-exit-command.mjs +38 -0
  69. package/js/examples/debug-exit-virtual.mjs +54 -0
  70. package/js/examples/debug-finish-consolidation.mjs +44 -0
  71. package/js/examples/debug-force-cleanup.mjs +47 -0
  72. package/js/examples/debug-getter-basic.mjs +13 -0
  73. package/js/examples/debug-getter-direct.mjs +23 -0
  74. package/js/examples/debug-getter-internals.mjs +61 -0
  75. package/js/examples/debug-handler-detection.mjs +65 -0
  76. package/js/examples/debug-idempotent-finish.mjs +54 -0
  77. package/js/examples/debug-idempotent-kill.mjs +58 -0
  78. package/js/examples/debug-interpolation-individual.mjs +88 -0
  79. package/js/examples/debug-interpolation-issue.mjs +101 -0
  80. package/js/examples/debug-jq-streaming.mjs +57 -0
  81. package/js/examples/debug-jq-tty-colors.mjs +168 -0
  82. package/js/examples/debug-kill-cleanup.mjs +56 -0
  83. package/js/examples/debug-kill-method.mjs +33 -0
  84. package/js/examples/debug-listener-interference.mjs +38 -0
  85. package/js/examples/debug-listener-lifecycle.mjs +50 -0
  86. package/js/examples/debug-listener-timing.mjs +62 -0
  87. package/js/examples/debug-listeners-property.mjs +34 -0
  88. package/js/examples/debug-map-methods.mjs +39 -0
  89. package/js/examples/debug-not-awaited-cleanup.mjs +106 -0
  90. package/js/examples/debug-off-method.mjs +28 -0
  91. package/js/examples/debug-option-merging.mjs +17 -0
  92. package/js/examples/debug-options.mjs +36 -0
  93. package/js/examples/debug-output.mjs +25 -0
  94. package/js/examples/debug-pattern-matching.mjs +69 -0
  95. package/js/examples/debug-pipeline-cat.mjs +28 -0
  96. package/js/examples/debug-pipeline-cleanup.mjs +71 -0
  97. package/js/examples/debug-pipeline-error-detailed.mjs +78 -0
  98. package/js/examples/debug-pipeline-error.mjs +65 -0
  99. package/js/examples/debug-pipeline-issue.mjs +26 -0
  100. package/js/examples/debug-pipeline-method.mjs +22 -0
  101. package/js/examples/debug-pipeline-stream.mjs +66 -0
  102. package/js/examples/debug-pipeline.mjs +14 -0
  103. package/js/examples/debug-process-exit-trace.mjs +41 -0
  104. package/js/examples/debug-process-path.mjs +38 -0
  105. package/js/examples/debug-property-check.mjs +29 -0
  106. package/js/examples/debug-resource-cleanup.mjs +83 -0
  107. package/js/examples/debug-shell-args.mjs +103 -0
  108. package/js/examples/debug-sigint-child-handler.mjs +44 -0
  109. package/js/examples/debug-sigint-forwarding.mjs +61 -0
  110. package/js/examples/debug-sigint-handler-install.mjs +79 -0
  111. package/js/examples/debug-sigint-handler-order.mjs +85 -0
  112. package/js/examples/debug-sigint-listeners.mjs +48 -0
  113. package/js/examples/debug-sigint-start-pattern.mjs +62 -0
  114. package/js/examples/debug-sigint-timer.mjs +40 -0
  115. package/js/examples/debug-simple-command.mjs +49 -0
  116. package/js/examples/debug-simple-getter.mjs +30 -0
  117. package/js/examples/debug-simple.mjs +39 -0
  118. package/js/examples/debug-simplified-finished.mjs +102 -0
  119. package/js/examples/debug-stack-overflow.mjs +25 -0
  120. package/js/examples/debug-stdin-simple.mjs +28 -0
  121. package/js/examples/debug-stdin.mjs +28 -0
  122. package/js/examples/debug-stream-emitter-isolated.mjs +45 -0
  123. package/js/examples/debug-stream-emitter.mjs +25 -0
  124. package/js/examples/debug-stream-events.mjs +70 -0
  125. package/js/examples/debug-stream-generator.mjs +23 -0
  126. package/js/examples/debug-stream-getter-issue.mjs +37 -0
  127. package/js/examples/debug-stream-getter.mjs +19 -0
  128. package/js/examples/debug-stream-internals.mjs +64 -0
  129. package/js/examples/debug-stream-method.mjs +46 -0
  130. package/js/examples/debug-stream-object.mjs +58 -0
  131. package/js/examples/debug-stream-properties.mjs +37 -0
  132. package/js/examples/debug-stream-timing.mjs +28 -0
  133. package/js/examples/debug-streaming.mjs +24 -0
  134. package/js/examples/debug-test-isolation.mjs +54 -0
  135. package/js/examples/debug-test-state.mjs +27 -0
  136. package/js/examples/debug-user-sigint.mjs +19 -0
  137. package/js/examples/debug-virtual-disable.mjs +21 -0
  138. package/js/examples/debug-virtual-vs-real.mjs +68 -0
  139. package/js/examples/debug-with-trace.mjs +23 -0
  140. package/js/examples/debug_parent_stream.mjs +22 -0
  141. package/js/examples/emulate-claude-stream.mjs +30 -0
  142. package/js/examples/emulated-streaming-direct.mjs +22 -0
  143. package/js/examples/emulated-streaming-jq-pipe.mjs +22 -0
  144. package/js/examples/emulated-streaming-sh-pipe.mjs +23 -0
  145. package/js/examples/events-build-process.mjs +73 -0
  146. package/js/examples/events-concurrent-streams.mjs +51 -0
  147. package/js/examples/events-error-handling.mjs +59 -0
  148. package/js/examples/events-file-monitoring.mjs +66 -0
  149. package/js/examples/events-interactive-simulation.mjs +69 -0
  150. package/js/examples/events-log-processing.mjs +72 -0
  151. package/js/examples/events-network-monitoring.mjs +68 -0
  152. package/js/examples/events-ping-basic.mjs +37 -0
  153. package/js/examples/events-progress-tracking.mjs +55 -0
  154. package/js/examples/events-stdin-input.mjs +34 -0
  155. package/js/examples/example-ansi-ls.mjs +39 -0
  156. package/js/examples/example-top.mjs +27 -0
  157. package/js/examples/final-ping-stdin-proof.mjs +88 -0
  158. package/js/examples/final-test-shell-operators.mjs +123 -0
  159. package/js/examples/final-working-examples.mjs +162 -0
  160. package/js/examples/gh-auth-test.mjs +103 -0
  161. package/js/examples/gh-delete-hang-test.mjs +79 -0
  162. package/js/examples/gh-gist-creation-test.mjs +276 -0
  163. package/js/examples/gh-gist-minimal-test.mjs +89 -0
  164. package/js/examples/gh-hang-exact-original.mjs +83 -0
  165. package/js/examples/gh-hang-reproduction.mjs +151 -0
  166. package/js/examples/gh-hang-test-with-redirect.mjs +45 -0
  167. package/js/examples/gh-hang-test-without-redirect.mjs +43 -0
  168. package/js/examples/gh-minimal-hang-check.mjs +33 -0
  169. package/js/examples/gh-operations-with-cd.mjs +187 -0
  170. package/js/examples/gh-output-test.mjs +102 -0
  171. package/js/examples/gh-reproduce-hang.mjs +41 -0
  172. package/js/examples/git-operations-with-cd.mjs +186 -0
  173. package/js/examples/interactive-math-calc.mjs +45 -0
  174. package/js/examples/interactive-top-fixed.mjs +25 -0
  175. package/js/examples/interactive-top-improved.mjs +72 -0
  176. package/js/examples/interactive-top-pty-logging.mjs +69 -0
  177. package/js/examples/interactive-top-pty.mjs +27 -0
  178. package/js/examples/interactive-top-with-logging.mjs +56 -0
  179. package/js/examples/interactive-top.mjs +29 -0
  180. package/js/examples/jq-color-demo.mjs +53 -0
  181. package/js/examples/jq-colors-streaming.mjs +42 -0
  182. package/js/examples/manual-ctrl-c-test.mjs +50 -0
  183. package/js/examples/methods-multiple-options.mjs +25 -0
  184. package/js/examples/methods-run-basic.mjs +10 -0
  185. package/js/examples/methods-start-basic.mjs +10 -0
  186. package/js/examples/node-compat-data-events.mjs +36 -0
  187. package/js/examples/node-compat-readable-event.mjs +29 -0
  188. package/js/examples/node-compat-small-buffer.mjs +33 -0
  189. package/js/examples/options-capture-false.mjs +12 -0
  190. package/js/examples/options-combined-settings.mjs +13 -0
  191. package/js/examples/options-custom-input.mjs +16 -0
  192. package/js/examples/options-default-behavior.mjs +10 -0
  193. package/js/examples/options-maximum-performance.mjs +15 -0
  194. package/js/examples/options-mirror-false.mjs +10 -0
  195. package/js/examples/options-performance-mode.mjs +13 -0
  196. package/js/examples/options-performance-optimization.mjs +14 -0
  197. package/js/examples/options-run-alias-demo.mjs +15 -0
  198. package/js/examples/options-run-alias.mjs +10 -0
  199. package/js/examples/options-silent-execution.mjs +14 -0
  200. package/js/examples/options-streaming-capture.mjs +24 -0
  201. package/js/examples/options-streaming-multiple.mjs +35 -0
  202. package/js/examples/options-streaming-silent.mjs +21 -0
  203. package/js/examples/options-streaming-stdin.mjs +21 -0
  204. package/js/examples/ping-streaming-filtered.mjs +22 -0
  205. package/js/examples/ping-streaming-interruptible.mjs +47 -0
  206. package/js/examples/ping-streaming-silent.mjs +24 -0
  207. package/js/examples/ping-streaming-simple.mjs +13 -0
  208. package/js/examples/ping-streaming-statistics.mjs +49 -0
  209. package/js/examples/ping-streaming-timestamps.mjs +22 -0
  210. package/js/examples/ping-streaming.mjs +48 -0
  211. package/js/examples/prove-ping-stdin-limitation.mjs +94 -0
  212. package/js/examples/readme-example.mjs +39 -0
  213. package/js/examples/realtime-json-stream.mjs +143 -0
  214. package/js/examples/reliable-stdin-commands.mjs +135 -0
  215. package/js/examples/reproduce-issue-135-v2.mjs +15 -0
  216. package/js/examples/reproduce-issue-135.mjs +17 -0
  217. package/js/examples/shell-cd-behavior.mjs +88 -0
  218. package/js/examples/sigint-forwarding-test.mjs +60 -0
  219. package/js/examples/sigint-handler-test.mjs +72 -0
  220. package/js/examples/simple-async-test.mjs +49 -0
  221. package/js/examples/simple-claude-test.mjs +17 -0
  222. package/js/examples/simple-event-test.mjs +33 -0
  223. package/js/examples/simple-jq-streaming.mjs +48 -0
  224. package/js/examples/simple-stream-demo.mjs +35 -0
  225. package/js/examples/simple-test-sleep.js +30 -0
  226. package/js/examples/simple-working-stdin.mjs +30 -0
  227. package/js/examples/streaming-behavior-test.mjs +116 -0
  228. package/js/examples/streaming-direct-command.mjs +21 -0
  229. package/js/examples/streaming-filtered-output.mjs +33 -0
  230. package/js/examples/streaming-grep-pipeline.mjs +21 -0
  231. package/js/examples/streaming-interactive-stdin.mjs +24 -0
  232. package/js/examples/streaming-jq-pipeline.mjs +23 -0
  233. package/js/examples/streaming-multistage-pipeline.mjs +23 -0
  234. package/js/examples/streaming-pipes-event-pattern.mjs +27 -0
  235. package/js/examples/streaming-pipes-multistage.mjs +22 -0
  236. package/js/examples/streaming-pipes-realtime-jq.mjs +23 -0
  237. package/js/examples/streaming-progress-tracking.mjs +34 -0
  238. package/js/examples/streaming-reusable-configs.mjs +52 -0
  239. package/js/examples/streaming-silent-capture.mjs +20 -0
  240. package/js/examples/streaming-test-simple.mjs +70 -0
  241. package/js/examples/streaming-virtual-pipeline.mjs +18 -0
  242. package/js/examples/syntax-basic-comparison.mjs +31 -0
  243. package/js/examples/syntax-basic-options.mjs +12 -0
  244. package/js/examples/syntax-combined-options.mjs +19 -0
  245. package/js/examples/syntax-command-chaining.mjs +12 -0
  246. package/js/examples/syntax-custom-directory.mjs +10 -0
  247. package/js/examples/syntax-custom-environment.mjs +13 -0
  248. package/js/examples/syntax-custom-stdin.mjs +10 -0
  249. package/js/examples/syntax-mixed-regular.mjs +11 -0
  250. package/js/examples/syntax-mixed-usage.mjs +15 -0
  251. package/js/examples/syntax-multiple-listeners.mjs +87 -0
  252. package/js/examples/syntax-piping-comparison.mjs +32 -0
  253. package/js/examples/syntax-reusable-config.mjs +16 -0
  254. package/js/examples/syntax-reusable-configs.mjs +21 -0
  255. package/js/examples/syntax-silent-operations.mjs +10 -0
  256. package/js/examples/syntax-stdin-option.mjs +12 -0
  257. package/js/examples/temp-sigint-test.mjs +21 -0
  258. package/js/examples/test-actual-buildshell.mjs +44 -0
  259. package/js/examples/test-async-streams-working.mjs +102 -0
  260. package/js/examples/test-async-streams.mjs +90 -0
  261. package/js/examples/test-auth-parse.mjs +74 -0
  262. package/js/examples/test-auto-quoting.mjs +57 -0
  263. package/js/examples/test-auto-start-fix.mjs +95 -0
  264. package/js/examples/test-baseline-sigint.mjs +38 -0
  265. package/js/examples/test-buffer-behavior.mjs +39 -0
  266. package/js/examples/test-buffers-simple.mjs +35 -0
  267. package/js/examples/test-bun-specific-issue.mjs +106 -0
  268. package/js/examples/test-bun-streaming.mjs +81 -0
  269. package/js/examples/test-cat-direct.mjs +41 -0
  270. package/js/examples/test-cat-pipe.mjs +34 -0
  271. package/js/examples/test-cd-behavior.mjs +42 -0
  272. package/js/examples/test-child-process-timing.mjs +53 -0
  273. package/js/examples/test-child-sigint-handler.mjs +62 -0
  274. package/js/examples/test-cleanup-simple.mjs +21 -0
  275. package/js/examples/test-comprehensive-tracing.mjs +58 -0
  276. package/js/examples/test-correct-space-handling.mjs +46 -0
  277. package/js/examples/test-ctrl-c-debug.mjs +44 -0
  278. package/js/examples/test-ctrl-c-inherit.mjs +30 -0
  279. package/js/examples/test-ctrl-c-sleep.mjs +31 -0
  280. package/js/examples/test-ctrl-c.mjs +17 -0
  281. package/js/examples/test-debug-new-options.mjs +55 -0
  282. package/js/examples/test-debug-pty.mjs +49 -0
  283. package/js/examples/test-debug-tee.mjs +38 -0
  284. package/js/examples/test-debug.mjs +25 -0
  285. package/js/examples/test-direct-jq.mjs +47 -0
  286. package/js/examples/test-direct-pipe-reading.mjs +119 -0
  287. package/js/examples/test-direct-pipe.sh +28 -0
  288. package/js/examples/test-double-quoting-prevention.mjs +138 -0
  289. package/js/examples/test-edge-cases-quoting.mjs +89 -0
  290. package/js/examples/test-events.mjs +37 -0
  291. package/js/examples/test-explicit-stdio.mjs +51 -0
  292. package/js/examples/test-final-streaming.mjs +71 -0
  293. package/js/examples/test-fix.mjs +71 -0
  294. package/js/examples/test-incremental-streaming.mjs +46 -0
  295. package/js/examples/test-individual-spawn.mjs +35 -0
  296. package/js/examples/test-inherit-stdout-not-stdin.mjs +133 -0
  297. package/js/examples/test-injection-protection.mjs +77 -0
  298. package/js/examples/test-interactive-streaming.mjs +140 -0
  299. package/js/examples/test-interactive-top.md +24 -0
  300. package/js/examples/test-interactive.mjs +17 -0
  301. package/js/examples/test-interpolation.mjs +14 -0
  302. package/js/examples/test-interrupt.mjs +40 -0
  303. package/js/examples/test-issue-135-comprehensive.mjs +41 -0
  304. package/js/examples/test-issue12-detailed.mjs +89 -0
  305. package/js/examples/test-issue12-exact.mjs +33 -0
  306. package/js/examples/test-jq-color.mjs +57 -0
  307. package/js/examples/test-jq-colors.mjs +41 -0
  308. package/js/examples/test-jq-compact.mjs +33 -0
  309. package/js/examples/test-jq-native.sh +10 -0
  310. package/js/examples/test-jq-pipeline-behavior.mjs +80 -0
  311. package/js/examples/test-jq-realtime.mjs +40 -0
  312. package/js/examples/test-manual-start.mjs +54 -0
  313. package/js/examples/test-mixed-quoting.mjs +88 -0
  314. package/js/examples/test-multi-stream.mjs +50 -0
  315. package/js/examples/test-multistage-debug.mjs +44 -0
  316. package/js/examples/test-native-spawn-vs-command-stream.mjs +154 -0
  317. package/js/examples/test-no-parse-pipeline.mjs +33 -0
  318. package/js/examples/test-non-virtual.mjs +52 -0
  319. package/js/examples/test-operators.mjs +53 -0
  320. package/js/examples/test-parent-continues.mjs +44 -0
  321. package/js/examples/test-path-interpolation.mjs +86 -0
  322. package/js/examples/test-ping-kill-and-stdin.mjs +98 -0
  323. package/js/examples/test-ping.mjs +12 -0
  324. package/js/examples/test-pty-spawn.mjs +101 -0
  325. package/js/examples/test-pty.mjs +38 -0
  326. package/js/examples/test-quote-behavior-summary.mjs +110 -0
  327. package/js/examples/test-quote-edge-cases.mjs +69 -0
  328. package/js/examples/test-quote-parsing.mjs +23 -0
  329. package/js/examples/test-raw-function.mjs +153 -0
  330. package/js/examples/test-raw-streaming.mjs +47 -0
  331. package/js/examples/test-readme-examples.mjs +142 -0
  332. package/js/examples/test-real-cat.mjs +28 -0
  333. package/js/examples/test-real-commands.mjs +21 -0
  334. package/js/examples/test-real-shell.mjs +31 -0
  335. package/js/examples/test-real-stdin-commands.mjs +160 -0
  336. package/js/examples/test-runner-batched.mjs +98 -0
  337. package/js/examples/test-runner-simple.mjs +80 -0
  338. package/js/examples/test-runner.mjs +67 -0
  339. package/js/examples/test-scope-parse.mjs +31 -0
  340. package/js/examples/test-sh-pipeline.mjs +24 -0
  341. package/js/examples/test-shell-detection.mjs +71 -0
  342. package/js/examples/test-shell-parser.mjs +37 -0
  343. package/js/examples/test-sigint-behavior.mjs +241 -0
  344. package/js/examples/test-sigint-handling.sh +14 -0
  345. package/js/examples/test-simple-pipe.mjs +12 -0
  346. package/js/examples/test-simple-streaming.mjs +32 -0
  347. package/js/examples/test-sleep-stdin.js +27 -0
  348. package/js/examples/test-sleep.mjs +56 -0
  349. package/js/examples/test-smart-quoting.mjs +180 -0
  350. package/js/examples/test-spaces-in-path.mjs +48 -0
  351. package/js/examples/test-special-chars-quoting.mjs +54 -0
  352. package/js/examples/test-stdin-after-start.mjs +39 -0
  353. package/js/examples/test-stdin-simple.mjs +67 -0
  354. package/js/examples/test-stdin-timing.mjs +74 -0
  355. package/js/examples/test-stdio-combinations.mjs +124 -0
  356. package/js/examples/test-stream-access.mjs +84 -0
  357. package/js/examples/test-stream-cleanup.mjs +27 -0
  358. package/js/examples/test-stream-readers.mjs +152 -0
  359. package/js/examples/test-streaming-final.mjs +57 -0
  360. package/js/examples/test-streaming-interfaces.mjs +141 -0
  361. package/js/examples/test-streaming-timing.mjs +27 -0
  362. package/js/examples/test-streaming.mjs +32 -0
  363. package/js/examples/test-streams-stdin-comprehensive.mjs +134 -0
  364. package/js/examples/test-streams-stdin-ctrl-c.mjs +96 -0
  365. package/js/examples/test-template-literal.mjs +26 -0
  366. package/js/examples/test-template-vs-interpolation.mjs +49 -0
  367. package/js/examples/test-timing.mjs +41 -0
  368. package/js/examples/test-top-inherit-stdout-stdin-control.mjs +123 -0
  369. package/js/examples/test-top-quit-stdin.mjs +118 -0
  370. package/js/examples/test-trace-option.mjs +21 -0
  371. package/js/examples/test-user-double-quotes.mjs +36 -0
  372. package/js/examples/test-user-single-quotes.mjs +36 -0
  373. package/js/examples/test-verbose.mjs +18 -0
  374. package/js/examples/test-verbose2.mjs +32 -0
  375. package/js/examples/test-virtual-streaming.mjs +125 -0
  376. package/js/examples/test-waiting-command.mjs +52 -0
  377. package/js/examples/test-waiting-commands.mjs +83 -0
  378. package/js/examples/test-watch-mode.mjs +104 -0
  379. package/js/examples/test-yes-cancellation.mjs +26 -0
  380. package/js/examples/test-yes-detailed.mjs +58 -0
  381. package/js/examples/test-yes-trace.mjs +28 -0
  382. package/js/examples/trace-abort-controller.mjs +30 -0
  383. package/js/examples/trace-error-handling.mjs +22 -0
  384. package/js/examples/trace-pipeline-command.mjs +22 -0
  385. package/js/examples/trace-signal-handling.mjs +35 -0
  386. package/js/examples/trace-simple-command.mjs +18 -0
  387. package/js/examples/trace-stderr-output.mjs +22 -0
  388. package/js/examples/verify-fix-both-runtimes.mjs +73 -0
  389. package/js/examples/verify-issue12-fixed.mjs +78 -0
  390. package/js/examples/which-command-common-commands.mjs +19 -0
  391. package/js/examples/which-command-gh-test.mjs +23 -0
  392. package/js/examples/which-command-nonexistent.mjs +20 -0
  393. package/js/examples/which-command-system-comparison.mjs +28 -0
  394. package/js/examples/working-example.mjs +13 -0
  395. package/js/examples/working-stdin-examples.mjs +138 -0
  396. package/js/examples/working-streaming-demo.mjs +49 -0
  397. package/{src → js/src}/$.mjs +20 -4
  398. package/{src → js/src}/$.utils.mjs +14 -2
  399. package/js/tests/$.features.test.mjs +283 -0
  400. package/js/tests/$.test.mjs +935 -0
  401. package/js/tests/builtin-commands.test.mjs +387 -0
  402. package/js/tests/bun-shell-path-fix.test.mjs +115 -0
  403. package/js/tests/bun.features.test.mjs +189 -0
  404. package/js/tests/cd-virtual-command.test.mjs +622 -0
  405. package/js/tests/cleanup-verification.test.mjs +127 -0
  406. package/js/tests/ctrl-c-baseline.test.mjs +207 -0
  407. package/js/tests/ctrl-c-basic.test.mjs +220 -0
  408. package/js/tests/ctrl-c-library.test.mjs +197 -0
  409. package/js/tests/ctrl-c-signal.test.mjs +915 -0
  410. package/js/tests/examples.test.mjs +252 -0
  411. package/js/tests/execa.features.test.mjs +198 -0
  412. package/js/tests/gh-commands.test.mjs +164 -0
  413. package/js/tests/gh-gist-operations.test.mjs +221 -0
  414. package/js/tests/git-gh-cd.test.mjs +466 -0
  415. package/js/tests/interactive-option.test.mjs +114 -0
  416. package/js/tests/interactive-streaming.test.mjs +307 -0
  417. package/js/tests/issue-135-final.test.mjs +58 -0
  418. package/js/tests/jq-color-behavior.test.mjs +140 -0
  419. package/js/tests/jq.test.mjs +318 -0
  420. package/js/tests/options-examples.test.mjs +106 -0
  421. package/js/tests/options-syntax.test.mjs +112 -0
  422. package/js/tests/path-interpolation.test.mjs +412 -0
  423. package/js/tests/pipe.test.mjs +291 -0
  424. package/js/tests/raw-function.test.mjs +266 -0
  425. package/js/tests/readme-examples.test.mjs +427 -0
  426. package/js/tests/resource-cleanup-internals.test.mjs +669 -0
  427. package/js/tests/shell-settings.test.mjs +279 -0
  428. package/js/tests/sigint-cleanup-isolated.test.mjs +151 -0
  429. package/js/tests/sigint-cleanup.test.mjs +118 -0
  430. package/js/tests/start-run-edge-cases.test.mjs +152 -0
  431. package/js/tests/start-run-options.test.mjs +181 -0
  432. package/js/tests/stderr-output-handling.test.mjs +279 -0
  433. package/js/tests/streaming-interfaces.test.mjs +194 -0
  434. package/js/tests/sync.test.mjs +297 -0
  435. package/js/tests/system-pipe.test.mjs +226 -0
  436. package/js/tests/test-cleanup.mjs +200 -0
  437. package/js/tests/test-helper-fixed.mjs +148 -0
  438. package/js/tests/test-helper-v2.mjs +118 -0
  439. package/js/tests/test-helper.mjs +171 -0
  440. package/js/tests/test-sigint-child.js +15 -0
  441. package/js/tests/text-method.test.mjs +225 -0
  442. package/js/tests/virtual.test.mjs +364 -0
  443. package/js/tests/yes-command-cleanup.test.mjs +208 -0
  444. package/js/tests/zx.features.test.mjs +233 -0
  445. package/package.json +13 -12
  446. package/rust/Cargo.lock +947 -0
  447. package/rust/Cargo.toml +47 -0
  448. package/rust/src/commands/basename.rs +69 -0
  449. package/rust/src/commands/cat.rs +123 -0
  450. package/rust/src/commands/cd.rs +67 -0
  451. package/rust/src/commands/cp.rs +187 -0
  452. package/rust/src/commands/dirname.rs +57 -0
  453. package/rust/src/commands/echo.rs +73 -0
  454. package/rust/src/commands/env.rs +33 -0
  455. package/rust/src/commands/exit.rs +36 -0
  456. package/rust/src/commands/false.rs +24 -0
  457. package/rust/src/commands/ls.rs +182 -0
  458. package/rust/src/commands/mkdir.rs +98 -0
  459. package/rust/src/commands/mod.rs +200 -0
  460. package/rust/src/commands/mv.rs +180 -0
  461. package/rust/src/commands/pwd.rs +28 -0
  462. package/rust/src/commands/rm.rs +150 -0
  463. package/rust/src/commands/seq.rs +179 -0
  464. package/rust/src/commands/sleep.rs +97 -0
  465. package/rust/src/commands/test.rs +204 -0
  466. package/rust/src/commands/touch.rs +99 -0
  467. package/rust/src/commands/true.rs +24 -0
  468. package/rust/src/commands/which.rs +87 -0
  469. package/rust/src/commands/yes.rs +99 -0
  470. package/rust/src/lib.rs +492 -0
  471. package/rust/src/main.rs +37 -0
  472. package/rust/src/shell_parser.rs +565 -0
  473. package/rust/src/utils.rs +335 -0
  474. package/rust/tests/builtin_commands.rs +549 -0
  475. package/rust/tests/process_runner.rs +286 -0
  476. package/rust/tests/shell_parser.rs +296 -0
  477. package/rust/tests/utils.rs +282 -0
  478. package/rust/tests/virtual_commands.rs +199 -0
  479. /package/{src → js/src}/commands/$.basename.mjs +0 -0
  480. /package/{src → js/src}/commands/$.cat.mjs +0 -0
  481. /package/{src → js/src}/commands/$.cd.mjs +0 -0
  482. /package/{src → js/src}/commands/$.cp.mjs +0 -0
  483. /package/{src → js/src}/commands/$.dirname.mjs +0 -0
  484. /package/{src → js/src}/commands/$.echo.mjs +0 -0
  485. /package/{src → js/src}/commands/$.env.mjs +0 -0
  486. /package/{src → js/src}/commands/$.exit.mjs +0 -0
  487. /package/{src → js/src}/commands/$.false.mjs +0 -0
  488. /package/{src → js/src}/commands/$.ls.mjs +0 -0
  489. /package/{src → js/src}/commands/$.mkdir.mjs +0 -0
  490. /package/{src → js/src}/commands/$.mv.mjs +0 -0
  491. /package/{src → js/src}/commands/$.pwd.mjs +0 -0
  492. /package/{src → js/src}/commands/$.rm.mjs +0 -0
  493. /package/{src → js/src}/commands/$.seq.mjs +0 -0
  494. /package/{src → js/src}/commands/$.sleep.mjs +0 -0
  495. /package/{src → js/src}/commands/$.test.mjs +0 -0
  496. /package/{src → js/src}/commands/$.touch.mjs +0 -0
  497. /package/{src → js/src}/commands/$.true.mjs +0 -0
  498. /package/{src → js/src}/commands/$.which.mjs +0 -0
  499. /package/{src → js/src}/commands/$.yes.mjs +0 -0
  500. /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
+ });