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