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,915 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
2
+ import { beforeTestCleanup, afterTestCleanup } from './test-cleanup.mjs';
3
+ import { spawn } from 'child_process';
4
+ import { trace } from '../src/$.utils.mjs';
5
+
6
+ // Platform detection - Windows handles signals differently than Unix
7
+ const isWindows = process.platform === 'win32';
8
+
9
+ // Skip entire describe block on Windows - SIGINT/signal handling is fundamentally different
10
+ describe.skipIf(isWindows)('CTRL+C Signal Handling', () => {
11
+ let childProcesses = [];
12
+
13
+ beforeEach(async () => {
14
+ await beforeTestCleanup();
15
+ });
16
+
17
+ // Log platform information for debugging
18
+ trace('SignalTest', () => `Platform: ${process.platform}`);
19
+ trace('SignalTest', () => `OS Release: ${require('os').release()}`);
20
+ trace('SignalTest', () => `Node Version: ${process.version}`);
21
+ trace('SignalTest', () => `CI Environment: ${process.env.CI || 'false'}`);
22
+
23
+ // Baseline test to verify that shell commands work in CI
24
+ it('BASELINE: should handle SIGINT with plain shell command', async () => {
25
+ trace(
26
+ 'SignalTest',
27
+ () => `Starting baseline SIGINT test on ${process.platform}`
28
+ );
29
+
30
+ const child = spawn('sh', ['-c', 'echo "BASELINE_START" && sleep 30'], {
31
+ stdio: ['pipe', 'pipe', 'pipe'],
32
+ detached: true,
33
+ });
34
+
35
+ trace('SignalTest', () => `Baseline child spawned, PID: ${child.pid}`);
36
+ childProcesses.push(child);
37
+
38
+ let stdout = '';
39
+ child.stdout.on('data', (data) => {
40
+ stdout += data.toString();
41
+ trace(
42
+ 'SignalTest',
43
+ () => `Baseline received stdout: ${JSON.stringify(data.toString())}`
44
+ );
45
+ });
46
+
47
+ // Wait for output
48
+ await new Promise((resolve) => setTimeout(resolve, 500));
49
+
50
+ // Send SIGINT (on macOS with detached:true, might need SIGTERM)
51
+ child.kill('SIGINT');
52
+
53
+ // Wait for exit with timeout
54
+ const { code, signal } = await new Promise((resolve) => {
55
+ let resolved = false;
56
+
57
+ child.on('exit', (code, signal) => {
58
+ if (!resolved) {
59
+ resolved = true;
60
+ resolve({ code, signal });
61
+ }
62
+ });
63
+
64
+ // On macOS, detached processes might not respond to SIGINT, use SIGTERM
65
+ setTimeout(() => {
66
+ if (!resolved) {
67
+ trace('SignalTest', 'SIGINT timeout, trying SIGTERM');
68
+ child.kill('SIGTERM');
69
+ }
70
+ }, 1000);
71
+
72
+ // Final fallback
73
+ setTimeout(() => {
74
+ if (!resolved) {
75
+ trace('SignalTest', 'SIGTERM timeout, using SIGKILL');
76
+ child.kill('SIGKILL');
77
+ }
78
+ }, 2000);
79
+ });
80
+
81
+ trace('SignalTest', () => `Baseline exit code: ${code} signal: ${signal}`);
82
+ trace('SignalTest', () => `Baseline stdout: ${stdout}`);
83
+
84
+ expect(stdout).toContain('BASELINE_START');
85
+ // On macOS with detached:true, processes might need SIGTERM/SIGKILL
86
+ // On Linux, it typically exits with code 130 for SIGINT
87
+ const validExit =
88
+ code === 130 ||
89
+ code === 143 ||
90
+ code === 0 ||
91
+ signal === 'SIGINT' ||
92
+ signal === 'SIGTERM' ||
93
+ signal === 'SIGKILL';
94
+ expect(validExit).toBe(true);
95
+ });
96
+
97
+ afterEach(async () => {
98
+ // Clean up any remaining child processes
99
+ childProcesses.forEach((child) => {
100
+ if (!child.killed) {
101
+ child.kill('SIGKILL');
102
+ }
103
+ });
104
+ childProcesses = [];
105
+
106
+ // Run test cleanup
107
+ await afterTestCleanup();
108
+ });
109
+
110
+ it(
111
+ 'should forward SIGINT to child process when external CTRL+C is sent',
112
+ async () => {
113
+ trace('SignalTest', 'Starting SIGINT forwarding test');
114
+ trace('SignalTest', () => `Current working directory: ${process.cwd()}`);
115
+
116
+ // Check if file exists first
117
+ const fs = await import('fs');
118
+ const path = await import('path');
119
+ const scriptPath = path.join(
120
+ process.cwd(),
121
+ 'js/examples',
122
+ 'test-sleep.mjs'
123
+ );
124
+ trace('SignalTest', () => `Script path: ${scriptPath}`);
125
+ trace('SignalTest', () => `Script exists: ${fs.existsSync(scriptPath)}`);
126
+
127
+ // Use the test-sleep.mjs which tests our actual library
128
+ const child = spawn('node', [scriptPath], {
129
+ stdio: ['pipe', 'pipe', 'pipe'],
130
+ detached: true,
131
+ cwd: process.cwd(), // Explicitly set working directory
132
+ });
133
+
134
+ trace('SignalTest', () => `Child process spawned, PID: ${child.pid}`);
135
+ childProcesses.push(child);
136
+
137
+ let stdout = '';
138
+ let stderr = '';
139
+ let dataReceived = false;
140
+ const stdoutChunks = [];
141
+
142
+ child.stdout.on('data', (data) => {
143
+ const chunk = data.toString();
144
+ stdout += chunk;
145
+ stdoutChunks.push({ time: Date.now(), data: chunk });
146
+ dataReceived = true;
147
+ trace(
148
+ 'SignalTest',
149
+ () => `Received stdout chunk: ${JSON.stringify(chunk)}`
150
+ );
151
+ });
152
+
153
+ child.stderr.on('data', (data) => {
154
+ const chunk = data.toString();
155
+ stderr += chunk;
156
+ trace('SignalTest', () => `Child stderr: ${chunk.trim()}`);
157
+ });
158
+
159
+ child.on('error', (error) => {
160
+ trace('SignalTest', () => `Child process error: ${error.message}`);
161
+ trace('SignalTest', () => `Error stack: ${error.stack}`);
162
+ });
163
+
164
+ child.on('spawn', () => {
165
+ trace('SignalTest', 'Child process spawned successfully');
166
+ });
167
+
168
+ // Wait for the process to start and output data
169
+ let attempts = 0;
170
+ while (!dataReceived && attempts < 20) {
171
+ await new Promise((resolve) => setTimeout(resolve, 100));
172
+ attempts++;
173
+ if (attempts % 5 === 0) {
174
+ trace('SignalTest', () => `Waiting for stdout, attempt: ${attempts}`);
175
+ }
176
+ }
177
+
178
+ trace(
179
+ 'SignalTest',
180
+ () => `Data received: ${dataReceived} after attempts: ${attempts}`
181
+ );
182
+ trace('SignalTest', () => `Current stdout length: ${stdout.length}`);
183
+ trace('SignalTest', () => `Current stderr length: ${stderr.length}`);
184
+
185
+ // Additional wait to ensure process is fully running
186
+ await new Promise((resolve) => setTimeout(resolve, 500));
187
+
188
+ // Send SIGINT to the child process (simulating CTRL+C)
189
+ trace('SignalTest', 'Sending SIGINT to child process');
190
+ const killResult = child.kill('SIGINT');
191
+ trace('SignalTest', () => `Kill result: ${killResult}`);
192
+
193
+ // Wait for the process to exit with robust handling
194
+ const exitCode = await new Promise((resolve) => {
195
+ let resolved = false;
196
+
197
+ child.on('close', (code, signal) => {
198
+ trace(
199
+ 'SignalTest',
200
+ () => `Child closed with code: ${code} signal: ${signal}`
201
+ );
202
+ if (!resolved) {
203
+ resolved = true;
204
+ resolve(code !== null ? code : signal === 'SIGINT' ? 130 : 1);
205
+ }
206
+ });
207
+
208
+ child.on('exit', (code, signal) => {
209
+ trace(
210
+ 'SignalTest',
211
+ () => `Child exited with code: ${code} signal: ${signal}`
212
+ );
213
+ if (!resolved) {
214
+ resolved = true;
215
+ resolve(code !== null ? code : signal === 'SIGINT' ? 130 : 1);
216
+ }
217
+ });
218
+
219
+ // Fallback timeout
220
+ setTimeout(() => {
221
+ if (!resolved) {
222
+ trace('SignalTest', 'Timeout reached, force killing child');
223
+ resolved = true;
224
+ child.kill('SIGKILL');
225
+ resolve(137);
226
+ }
227
+ }, 3000);
228
+ });
229
+
230
+ // Should exit with SIGINT code (130) due to our signal handling
231
+ trace('SignalTest', () => `First test exit code: ${exitCode}`);
232
+ trace('SignalTest', () => `First test stdout length: ${stdout.length}`);
233
+ if (stdout.length === 0) {
234
+ trace('SignalTest', 'WARNING: No stdout captured!');
235
+ trace('SignalTest', () => `stderr content: ${stderr}`);
236
+ trace(
237
+ 'SignalTest',
238
+ () => `stdout chunks received: ${JSON.stringify(stdoutChunks)}`
239
+ );
240
+ } else {
241
+ trace('SignalTest', () => `stdout content: ${JSON.stringify(stdout)}`);
242
+ }
243
+
244
+ expect([130, 143, 137].includes(exitCode) || exitCode > 0).toBe(true);
245
+
246
+ // Should have started sleep successfully before being interrupted
247
+ expect(stdout).toContain('STARTING_SLEEP');
248
+ },
249
+ { timeout: 10000 }
250
+ );
251
+
252
+ it(
253
+ 'should not interfere with user SIGINT handling when no children active',
254
+ async () => {
255
+ // Use inline Node.js code for better CI reliability
256
+ const nodeCode = `
257
+ process.on('SIGINT', () => {
258
+ console.log('USER_SIGINT_HANDLER_CALLED');
259
+ process.exit(42);
260
+ });
261
+ console.log('Process started, waiting for SIGINT...');
262
+ setTimeout(() => {
263
+ console.log('TIMEOUT_REACHED');
264
+ process.exit(1);
265
+ }, 5000);
266
+ `;
267
+ const child = spawn('node', ['-e', nodeCode], {
268
+ stdio: ['pipe', 'pipe', 'pipe'],
269
+ detached: true,
270
+ });
271
+
272
+ childProcesses.push(child);
273
+
274
+ let stdout = '';
275
+ let stderr = '';
276
+
277
+ child.stdout.on('data', (data) => {
278
+ stdout += data.toString();
279
+ });
280
+
281
+ child.stderr.on('data', (data) => {
282
+ stderr += data.toString();
283
+ });
284
+
285
+ // Give the process time to set up its signal handler
286
+ await new Promise((resolve) => setTimeout(resolve, 500));
287
+
288
+ // Send SIGINT to the process
289
+ child.kill('SIGINT');
290
+
291
+ // Wait for the process to exit with robust handling
292
+ const exitCode = await new Promise((resolve) => {
293
+ let resolved = false;
294
+
295
+ child.on('close', (code, signal) => {
296
+ if (!resolved) {
297
+ resolved = true;
298
+ resolve(code !== null ? code : signal === 'SIGINT' ? 130 : 1);
299
+ }
300
+ });
301
+
302
+ child.on('exit', (code, signal) => {
303
+ if (!resolved) {
304
+ resolved = true;
305
+ resolve(code !== null ? code : signal === 'SIGINT' ? 130 : 1);
306
+ }
307
+ });
308
+
309
+ // Fallback timeout
310
+ setTimeout(() => {
311
+ if (!resolved) {
312
+ resolved = true;
313
+ child.kill('SIGKILL');
314
+ resolve(137);
315
+ }
316
+ }, 3000);
317
+ });
318
+
319
+ // Should exit with user's custom exit code (42)
320
+ expect(exitCode).toBe(42);
321
+ expect(stdout).toContain('USER_SIGINT_HANDLER_CALLED');
322
+ expect(stdout).not.toContain('TIMEOUT_REACHED');
323
+ },
324
+ { timeout: 5000 }
325
+ );
326
+
327
+ it(
328
+ 'should handle SIGINT in long-running commands via API',
329
+ async () => {
330
+ // This test uses the $ API directly but doesn't send signals to the test process
331
+ const { $ } = await import('../src/$.mjs');
332
+
333
+ // Start a long-running command
334
+ const runner = $`sleep 10`;
335
+ const commandPromise = runner.start();
336
+
337
+ // Give it time to start
338
+ await new Promise((resolve) => setTimeout(resolve, 200));
339
+
340
+ // Kill the runner directly (this simulates what happens when SIGINT is forwarded)
341
+ runner.kill();
342
+
343
+ // Wait for command to finish
344
+ const result = await commandPromise;
345
+
346
+ // Verify the command was interrupted with proper exit code
347
+ expect(result.code).toBe(143); // SIGTERM exit code
348
+ },
349
+ { timeout: 10000 }
350
+ );
351
+
352
+ it(
353
+ 'should handle multiple concurrent processes receiving signals',
354
+ async () => {
355
+ const { $ } = await import('../src/$.mjs');
356
+ const runners = [];
357
+ const promises = [];
358
+
359
+ // Start multiple long-running commands
360
+ for (let i = 0; i < 3; i++) {
361
+ const runner = $`sleep 10`;
362
+ runners.push(runner);
363
+ promises.push(runner.start());
364
+ }
365
+
366
+ // Give them time to start
367
+ await new Promise((resolve) => setTimeout(resolve, 500));
368
+
369
+ // Kill all runners (simulating SIGINT forwarding)
370
+ runners.forEach((runner) => runner.kill());
371
+
372
+ // Wait for all to finish
373
+ const results = await Promise.all(promises);
374
+
375
+ // All should have been interrupted with proper exit code
376
+ expect(results.length).toBe(3);
377
+ results.forEach((result) => {
378
+ expect(result.code).toBe(143); // SIGTERM exit code
379
+ });
380
+ },
381
+ { timeout: 10000 }
382
+ );
383
+
384
+ it(
385
+ 'should properly handle signals in external process with sleep',
386
+ async () => {
387
+ // Use a simple shell script for CI reliability
388
+ const child = spawn(
389
+ 'sh',
390
+ ['-c', 'echo "STARTING_SLEEP" && sleep 10 && echo "SLEEP_COMPLETED"'],
391
+ {
392
+ stdio: ['pipe', 'pipe', 'pipe'],
393
+ detached: true,
394
+ }
395
+ );
396
+
397
+ childProcesses.push(child);
398
+
399
+ let stdout = '';
400
+ let stderr = '';
401
+
402
+ child.stdout.on('data', (data) => {
403
+ stdout += data.toString();
404
+ });
405
+
406
+ child.stderr.on('data', (data) => {
407
+ stderr += data.toString();
408
+ });
409
+
410
+ // Wait for sleep to start
411
+ await new Promise((resolve) => setTimeout(resolve, 1000));
412
+
413
+ // Send SIGINT to the process
414
+ child.kill('SIGINT');
415
+
416
+ // Wait for the process to exit with robust handling
417
+ const exitCode = await new Promise((resolve) => {
418
+ let resolved = false;
419
+
420
+ child.on('close', (code, signal) => {
421
+ if (!resolved) {
422
+ resolved = true;
423
+ resolve(code !== null ? code : signal === 'SIGINT' ? 130 : 1);
424
+ }
425
+ });
426
+
427
+ child.on('exit', (code, signal) => {
428
+ if (!resolved) {
429
+ resolved = true;
430
+ resolve(code !== null ? code : signal === 'SIGINT' ? 130 : 1);
431
+ }
432
+ });
433
+
434
+ // Fallback timeout
435
+ setTimeout(() => {
436
+ if (!resolved) {
437
+ resolved = true;
438
+ child.kill('SIGKILL');
439
+ resolve(137);
440
+ }
441
+ }, 3000);
442
+ });
443
+
444
+ // Should exit with SIGINT code due to our signal handling
445
+ trace('SignalTest', () => `Third test exit code: ${exitCode}`);
446
+ expect([130, 143, 137].includes(exitCode) || exitCode > 0).toBe(true);
447
+ expect(stdout).toContain('STARTING_SLEEP');
448
+ expect(stdout).not.toContain('SLEEP_COMPLETED');
449
+ },
450
+ { timeout: 10000 }
451
+ );
452
+
453
+ it(
454
+ 'should not interfere with child process signal handlers',
455
+ async () => {
456
+ // Create a script that has its own SIGINT handler for cleanup (no ES modules)
457
+ const nodeCode = `
458
+ let cleanupDone = false;
459
+ process.on('SIGINT', () => {
460
+ console.log('CHILD_CLEANUP_START');
461
+ // Simulate cleanup work
462
+ setTimeout(() => {
463
+ cleanupDone = true;
464
+ console.log('CHILD_CLEANUP_DONE');
465
+ process.exit(0); // Exit cleanly after cleanup
466
+ }, 100);
467
+ });
468
+
469
+ console.log('CHILD_READY');
470
+
471
+ // Keep process alive
472
+ setTimeout(() => {
473
+ console.log('TIMEOUT_REACHED');
474
+ process.exit(1);
475
+ }, 5000);
476
+ `;
477
+ const child = spawn('node', ['-e', nodeCode], {
478
+ stdio: ['pipe', 'pipe', 'pipe'],
479
+ detached: true,
480
+ });
481
+
482
+ childProcesses.push(child);
483
+
484
+ let stdout = '';
485
+ let stderr = '';
486
+
487
+ child.stdout.on('data', (data) => {
488
+ stdout += data.toString();
489
+ });
490
+
491
+ child.stderr.on('data', (data) => {
492
+ stderr += data.toString();
493
+ });
494
+
495
+ // Wait for child to be ready
496
+ await new Promise((resolve) => setTimeout(resolve, 1000));
497
+
498
+ // Send SIGINT to the process
499
+ child.kill('SIGINT');
500
+
501
+ // Wait for the process to exit with robust handling
502
+ const exitCode = await new Promise((resolve) => {
503
+ let resolved = false;
504
+
505
+ child.on('close', (code, signal) => {
506
+ if (!resolved) {
507
+ resolved = true;
508
+ resolve(code !== null ? code : signal === 'SIGINT' ? 130 : 1);
509
+ }
510
+ });
511
+
512
+ child.on('exit', (code, signal) => {
513
+ if (!resolved) {
514
+ resolved = true;
515
+ resolve(code !== null ? code : signal === 'SIGINT' ? 130 : 1);
516
+ }
517
+ });
518
+
519
+ // Fallback timeout
520
+ setTimeout(() => {
521
+ if (!resolved) {
522
+ resolved = true;
523
+ child.kill('SIGKILL');
524
+ resolve(137);
525
+ }
526
+ }, 3000);
527
+ });
528
+
529
+ // Should exit with exit code 0 since child has its own SIGINT handler that calls process.exit(0)
530
+ // The child should have started its cleanup handler
531
+ trace('SignalTest', () => `Fourth test exit code: ${exitCode}`);
532
+ expect(exitCode).toBe(0); // This test is specifically for children with custom signal handlers
533
+ expect(stdout).toContain('CHILD_READY');
534
+ expect(stdout).toContain('CHILD_CLEANUP_START');
535
+ // Note: CHILD_CLEANUP_DONE might not appear if the process is killed during cleanup
536
+ // This is realistic behavior when external SIGINT is sent
537
+ },
538
+ { timeout: 10000 }
539
+ );
540
+ });
541
+
542
+ // Skip entire describe block on Windows - uses 'sh' shell and Unix signals
543
+ describe.skipIf(isWindows)('CTRL+C with Different stdin Modes', () => {
544
+ let childProcesses = [];
545
+
546
+ beforeEach(async () => {
547
+ await beforeTestCleanup();
548
+ });
549
+
550
+ afterEach(async () => {
551
+ // Clean up any remaining child processes
552
+ childProcesses.forEach((child) => {
553
+ if (!child.killed) {
554
+ child.kill('SIGKILL');
555
+ }
556
+ });
557
+ childProcesses = [];
558
+
559
+ // Run test cleanup
560
+ await afterTestCleanup();
561
+ });
562
+
563
+ it(
564
+ 'should handle kill regardless of stdin mode',
565
+ async () => {
566
+ const { $ } = await import('../src/$.mjs');
567
+
568
+ // Test 1: Default stdin (inherit)
569
+ const runner1 = $`sleep 3`;
570
+ const promise1 = runner1.start();
571
+ await new Promise((resolve) => setTimeout(resolve, 300));
572
+ runner1.kill();
573
+ const result1 = await promise1;
574
+ expect(result1.code).toBe(143); // SIGTERM exit code
575
+
576
+ // Test 2: With stdin set to a string using new syntax
577
+ const runner2 = $({ stdin: 'some input data' })`sleep 3`;
578
+ const promise2 = runner2.start();
579
+ await new Promise((resolve) => setTimeout(resolve, 300));
580
+ runner2.kill();
581
+ const result2 = await promise2;
582
+ expect(result2.code).toBe(143); // SIGTERM exit code
583
+
584
+ // Test 3: With stdin set to ignore using new syntax
585
+ const runner3 = $({ stdin: 'ignore' })`sleep 3`;
586
+ const promise3 = runner3.start();
587
+ await new Promise((resolve) => setTimeout(resolve, 300));
588
+ runner3.kill();
589
+ const result3 = await promise3;
590
+ expect(result3.code).toBe(143); // SIGTERM exit code
591
+ },
592
+ { timeout: 15000 }
593
+ );
594
+
595
+ it(
596
+ 'should properly clean up stdin forwarding on external SIGINT',
597
+ async () => {
598
+ // Test that stdin forwarding is properly cleaned up when external SIGINT is sent
599
+ // Use simple shell command for CI reliability
600
+ const child = spawn('sh', ['-c', 'echo "RUNNING_COMMAND" && sleep 3'], {
601
+ stdio: ['pipe', 'pipe', 'pipe'],
602
+ detached: true,
603
+ });
604
+
605
+ childProcesses.push(child);
606
+
607
+ let stdout = '';
608
+ let stderr = '';
609
+
610
+ child.stdout.on('data', (data) => {
611
+ stdout += data.toString();
612
+ });
613
+
614
+ child.stderr.on('data', (data) => {
615
+ stderr += data.toString();
616
+ });
617
+
618
+ // Give it time to set up stdin forwarding
619
+ await new Promise((resolve) => setTimeout(resolve, 500));
620
+
621
+ // Send SIGINT to interrupt the cat command
622
+ child.kill('SIGINT');
623
+
624
+ // Wait for the process to exit with robust handling
625
+ const exitCode = await new Promise((resolve) => {
626
+ let resolved = false;
627
+
628
+ child.on('close', (code, signal) => {
629
+ if (!resolved) {
630
+ resolved = true;
631
+ resolve(code !== null ? code : signal === 'SIGINT' ? 130 : 1);
632
+ }
633
+ });
634
+
635
+ child.on('exit', (code, signal) => {
636
+ if (!resolved) {
637
+ resolved = true;
638
+ resolve(code !== null ? code : signal === 'SIGINT' ? 130 : 1);
639
+ }
640
+ });
641
+
642
+ // Fallback timeout
643
+ setTimeout(() => {
644
+ if (!resolved) {
645
+ resolved = true;
646
+ child.kill('SIGKILL');
647
+ resolve(137);
648
+ }
649
+ }, 3000);
650
+ });
651
+
652
+ // Should exit with SIGINT code or clean exit (both are acceptable for stdin cleanup tests)
653
+ trace('SignalTest', () => `Fifth test exit code: ${exitCode}`);
654
+ expect(typeof exitCode).toBe('number'); // Just ensure we get a valid exit code
655
+ expect(stdout).toContain('RUNNING_COMMAND');
656
+ // The important thing is that the process is properly cleaned up
657
+ // This is handled by our signal forwarding cleanup
658
+ },
659
+ { timeout: 10000 }
660
+ );
661
+
662
+ it(
663
+ 'should handle parent stream closure triggering process cleanup',
664
+ async () => {
665
+ // Test parent stream closure handling mechanism
666
+ const child = spawn(
667
+ 'node',
668
+ [
669
+ '-e',
670
+ `
671
+ import { $ } from './js/src/$.mjs';
672
+
673
+ // Start a long-running command
674
+ const runner = $\`sleep 5\`;
675
+ const promise = runner.start();
676
+
677
+ // Simulate parent stream closure after a delay
678
+ setTimeout(() => {
679
+ console.log('SIMULATING_PARENT_STREAM_CLOSURE');
680
+ process.stdout.destroy(); // This should trigger cleanup
681
+ }, 1000);
682
+
683
+ try {
684
+ await promise;
685
+ console.log('COMMAND_COMPLETED');
686
+ } catch (error) {
687
+ console.log('COMMAND_INTERRUPTED');
688
+ }
689
+ `,
690
+ ],
691
+ {
692
+ stdio: ['pipe', 'pipe', 'pipe'],
693
+ detached: true,
694
+ }
695
+ );
696
+
697
+ childProcesses.push(child);
698
+
699
+ let stdout = '';
700
+ child.stdout.on('data', (data) => {
701
+ stdout += data.toString();
702
+ });
703
+
704
+ // Wait for the process to complete
705
+ const exitCode = await new Promise((resolve) => {
706
+ child.on('close', (code) => {
707
+ resolve(code);
708
+ });
709
+ });
710
+
711
+ // Should have detected parent stream closure and exited
712
+ expect(stdout).toContain('SIMULATING_PARENT_STREAM_CLOSURE');
713
+ expect(typeof exitCode).toBe('number'); // Should have a valid exit code
714
+ },
715
+ { timeout: 10000 }
716
+ );
717
+
718
+ it(
719
+ 'should bypass virtual commands with custom stdin for proper signal handling',
720
+ async () => {
721
+ // Test the bypass logic for built-in commands with custom stdin
722
+ const child = spawn(
723
+ 'node',
724
+ [
725
+ '-e',
726
+ `
727
+ import { $ } from './js/src/$.mjs';
728
+
729
+ console.log('STARTING_SLEEP_WITH_CUSTOM_STDIN');
730
+
731
+ try {
732
+ // This should bypass virtual sleep and use real /usr/bin/sleep
733
+ const result = await \$({ stdin: 'custom input' })\`sleep 2\`;
734
+ console.log('SLEEP_COMPLETED: ' + result.code);
735
+ } catch (error) {
736
+ console.log('SLEEP_ERROR: ' + error.message);
737
+ }
738
+ `,
739
+ ],
740
+ {
741
+ stdio: ['pipe', 'pipe', 'pipe'],
742
+ detached: true,
743
+ }
744
+ );
745
+
746
+ childProcesses.push(child);
747
+
748
+ let stdout = '';
749
+ child.stdout.on('data', (data) => {
750
+ stdout += data.toString();
751
+ });
752
+
753
+ // Give it time to start then interrupt
754
+ await new Promise((resolve) => setTimeout(resolve, 500));
755
+ child.kill('SIGINT');
756
+
757
+ const exitCode = await new Promise((resolve) => {
758
+ child.on('close', (code) => {
759
+ resolve(code);
760
+ });
761
+ });
762
+
763
+ trace('SignalTest', () => `Sixth test exit code: ${exitCode}`);
764
+ expect([130, 143, 137].includes(exitCode) || exitCode > 0).toBe(true); // SIGINT exit code
765
+ expect(stdout).toContain('STARTING_SLEEP_WITH_CUSTOM_STDIN');
766
+ },
767
+ { timeout: 10000 }
768
+ );
769
+
770
+ it(
771
+ 'should handle Bun vs Node.js signal differences',
772
+ async () => {
773
+ // Test platform-specific signal handling
774
+ const child = spawn(
775
+ 'node',
776
+ [
777
+ '-e',
778
+ `
779
+ import { $ } from './js/src/$.mjs';
780
+
781
+ const isBun = typeof globalThis.Bun !== 'undefined';
782
+ console.log('RUNTIME: ' + (isBun ? 'BUN' : 'NODE'));
783
+
784
+ try {
785
+ const result = await $\`sleep 2\`;
786
+ console.log('SLEEP_COMPLETED: ' + result.code);
787
+ } catch (error) {
788
+ console.log('SLEEP_ERROR: ' + error.message);
789
+ }
790
+ `,
791
+ ],
792
+ {
793
+ stdio: ['pipe', 'pipe', 'pipe'],
794
+ detached: true,
795
+ }
796
+ );
797
+
798
+ childProcesses.push(child);
799
+
800
+ let stdout = '';
801
+ child.stdout.on('data', (data) => {
802
+ stdout += data.toString();
803
+ });
804
+
805
+ // Let it run for a bit then interrupt
806
+ await new Promise((resolve) => setTimeout(resolve, 1000));
807
+ child.kill('SIGINT');
808
+
809
+ const exitCode = await new Promise((resolve) => {
810
+ child.on('close', (code) => {
811
+ resolve(code);
812
+ });
813
+ });
814
+
815
+ trace('SignalTest', () => `Seventh test exit code: ${exitCode}`);
816
+ expect(typeof exitCode).toBe('number'); // Platform differences may result in various exit codes
817
+ expect(stdout).toMatch(/RUNTIME: (BUN|NODE)/);
818
+ },
819
+ { timeout: 10000 }
820
+ );
821
+
822
+ // REGRESSION TEST: Core issue that was fixed
823
+ it(
824
+ 'should properly cancel virtual commands and respect user SIGINT handlers (regression test)',
825
+ async () => {
826
+ // This test prevents regression of the core issue where:
827
+ // 1. Virtual commands (sleep) weren't being cancelled by SIGINT
828
+ // 2. SIGINT handler was interfering with user-defined handlers
829
+ // 3. Processes weren't outputting expected logs before interruption
830
+
831
+ // Test 1: Virtual command cancellation with proper exit codes
832
+ trace('SignalTest', 'Testing virtual command SIGINT cancellation...');
833
+ const child1 = spawn('node', ['js/examples/test-sleep.mjs'], {
834
+ stdio: ['pipe', 'pipe', 'pipe'],
835
+ detached: true,
836
+ });
837
+
838
+ let stdout1 = '';
839
+ child1.stdout.on('data', (data) => {
840
+ stdout1 += data.toString();
841
+ });
842
+
843
+ // Wait for command to start, then interrupt
844
+ await new Promise((resolve) => setTimeout(resolve, 500));
845
+ child1.kill('SIGINT');
846
+
847
+ const exitCode1 = await new Promise((resolve) => {
848
+ child1.on('close', (code) => resolve(code));
849
+ });
850
+
851
+ // Should capture startup output and exit with SIGINT code
852
+ expect(stdout1).toContain('STARTING_SLEEP');
853
+ expect(stdout1).not.toContain('SLEEP_COMPLETED');
854
+ expect(exitCode1).toBe(130); // 128 + 2 (SIGINT)
855
+ trace('SignalTest', '✓ Virtual command properly cancelled with SIGINT');
856
+
857
+ // Test 2: User SIGINT handler cooperation
858
+ trace('SignalTest', 'Testing user SIGINT handler cooperation...');
859
+ const child2 = spawn(
860
+ 'node',
861
+ [
862
+ '-e',
863
+ `
864
+ import { $ } from './js/src/$.mjs';
865
+
866
+ // Set up user's SIGINT handler AFTER importing our library
867
+ process.on('SIGINT', () => {
868
+ console.log('USER_HANDLER_EXECUTED');
869
+ process.exit(42);
870
+ });
871
+
872
+ console.log('PROCESS_READY');
873
+
874
+ // Run a virtual command that will be interrupted
875
+ try {
876
+ await \$\`sleep 5\`;
877
+ console.log('SLEEP_FINISHED');
878
+ } catch (err) {
879
+ console.log('SLEEP_ERROR');
880
+ }
881
+ `,
882
+ ],
883
+ {
884
+ stdio: ['pipe', 'pipe', 'pipe'],
885
+ detached: true,
886
+ }
887
+ );
888
+
889
+ let stdout2 = '';
890
+ child2.stdout.on('data', (data) => {
891
+ stdout2 += data.toString();
892
+ });
893
+
894
+ // Wait for setup, then interrupt
895
+ await new Promise((resolve) => setTimeout(resolve, 500));
896
+ child2.kill('SIGINT');
897
+
898
+ const exitCode2 = await new Promise((resolve) => {
899
+ child2.on('close', (code) => resolve(code));
900
+ });
901
+
902
+ // User's handler should take precedence
903
+ expect(stdout2).toContain('PROCESS_READY');
904
+ expect(stdout2).toContain('USER_HANDLER_EXECUTED');
905
+ expect(exitCode2).toBe(42); // User's custom exit code
906
+ trace('SignalTest', '✓ User SIGINT handler properly executed');
907
+
908
+ trace(
909
+ 'SignalTest',
910
+ '🎉 Regression test passed - core issues remain fixed'
911
+ );
912
+ },
913
+ { timeout: 15000 }
914
+ );
915
+ });