@volter/tabnode 0.2.14-volter.88

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 (429) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +78 -0
  3. package/THIRD-PARTY-NOTICES.md +12 -0
  4. package/dist/__sw__.js +526 -0
  5. package/dist/assets/runtime-worker-4MKaHFVb.js +75221 -0
  6. package/dist/assets/runtime-worker-4MKaHFVb.js.map +1 -0
  7. package/dist/code-transforms.d.ts +21 -0
  8. package/dist/code-transforms.d.ts.map +1 -0
  9. package/dist/config/cdn.d.ts +12 -0
  10. package/dist/config/cdn.d.ts.map +1 -0
  11. package/dist/create-runtime.d.ts +22 -0
  12. package/dist/create-runtime.d.ts.map +1 -0
  13. package/dist/host-globals.d.ts +62 -0
  14. package/dist/host-globals.d.ts.map +1 -0
  15. package/dist/index.cjs +36021 -0
  16. package/dist/index.cjs.map +1 -0
  17. package/dist/index.d.ts +127 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.mjs +36020 -0
  20. package/dist/index.mjs.map +1 -0
  21. package/dist/node-internals.d.ts +68 -0
  22. package/dist/node-internals.d.ts.map +1 -0
  23. package/dist/node-lib/binding/blob.d.ts +12 -0
  24. package/dist/node-lib/binding/blob.d.ts.map +1 -0
  25. package/dist/node-lib/binding/buffer.d.ts +144 -0
  26. package/dist/node-lib/binding/buffer.d.ts.map +1 -0
  27. package/dist/node-lib/binding/cares_wrap.d.ts +14 -0
  28. package/dist/node-lib/binding/cares_wrap.d.ts.map +1 -0
  29. package/dist/node-lib/binding/fds.d.ts +30 -0
  30. package/dist/node-lib/binding/fds.d.ts.map +1 -0
  31. package/dist/node-lib/binding/fs.d.ts +157 -0
  32. package/dist/node-lib/binding/fs.d.ts.map +1 -0
  33. package/dist/node-lib/binding/handles.d.ts +81 -0
  34. package/dist/node-lib/binding/handles.d.ts.map +1 -0
  35. package/dist/node-lib/binding/http_parser.d.ts +122 -0
  36. package/dist/node-lib/binding/http_parser.d.ts.map +1 -0
  37. package/dist/node-lib/binding/index.d.ts +3 -0
  38. package/dist/node-lib/binding/index.d.ts.map +1 -0
  39. package/dist/node-lib/binding/llhttp-wasm.d.ts +22 -0
  40. package/dist/node-lib/binding/llhttp-wasm.d.ts.map +1 -0
  41. package/dist/node-lib/binding/misc.d.ts +123 -0
  42. package/dist/node-lib/binding/misc.d.ts.map +1 -0
  43. package/dist/node-lib/binding/pipe_wrap.d.ts +84 -0
  44. package/dist/node-lib/binding/pipe_wrap.d.ts.map +1 -0
  45. package/dist/node-lib/binding/process_wrap.d.ts +173 -0
  46. package/dist/node-lib/binding/process_wrap.d.ts.map +1 -0
  47. package/dist/node-lib/binding/spawn_sync.d.ts +30 -0
  48. package/dist/node-lib/binding/spawn_sync.d.ts.map +1 -0
  49. package/dist/node-lib/binding/stream_wrap.d.ts +191 -0
  50. package/dist/node-lib/binding/stream_wrap.d.ts.map +1 -0
  51. package/dist/node-lib/binding/stubs.d.ts +23 -0
  52. package/dist/node-lib/binding/stubs.d.ts.map +1 -0
  53. package/dist/node-lib/binding/tcp_wrap.d.ts +98 -0
  54. package/dist/node-lib/binding/tcp_wrap.d.ts.map +1 -0
  55. package/dist/node-lib/binding/tty_wrap.d.ts +36 -0
  56. package/dist/node-lib/binding/tty_wrap.d.ts.map +1 -0
  57. package/dist/node-lib/binding/util.d.ts +126 -0
  58. package/dist/node-lib/binding/util.d.ts.map +1 -0
  59. package/dist/node-lib/binding/uv.d.ts +32 -0
  60. package/dist/node-lib/binding/uv.d.ts.map +1 -0
  61. package/dist/node-lib/binding/zlib-pako.d.ts +22 -0
  62. package/dist/node-lib/binding/zlib-pako.d.ts.map +1 -0
  63. package/dist/node-lib/binding/zlib.d.ts +38 -0
  64. package/dist/node-lib/binding/zlib.d.ts.map +1 -0
  65. package/dist/node-lib/buffer-module.d.ts +51 -0
  66. package/dist/node-lib/buffer-module.d.ts.map +1 -0
  67. package/dist/node-lib/child-process-module.d.ts +26 -0
  68. package/dist/node-lib/child-process-module.d.ts.map +1 -0
  69. package/dist/node-lib/events-module.d.ts +58 -0
  70. package/dist/node-lib/events-module.d.ts.map +1 -0
  71. package/dist/node-lib/fs-module.d.ts +5 -0
  72. package/dist/node-lib/fs-module.d.ts.map +1 -0
  73. package/dist/node-lib/http-bridge.d.ts +43 -0
  74. package/dist/node-lib/http-bridge.d.ts.map +1 -0
  75. package/dist/node-lib/http-module.d.ts +35 -0
  76. package/dist/node-lib/http-module.d.ts.map +1 -0
  77. package/dist/node-lib/internals/addresses.d.ts +40 -0
  78. package/dist/node-lib/internals/addresses.d.ts.map +1 -0
  79. package/dist/node-lib/internals/buffer-and-streams.d.ts +96 -0
  80. package/dist/node-lib/internals/buffer-and-streams.d.ts.map +1 -0
  81. package/dist/node-lib/internals/child-process.d.ts +27 -0
  82. package/dist/node-lib/internals/child-process.d.ts.map +1 -0
  83. package/dist/node-lib/internals/events-util.d.ts +83 -0
  84. package/dist/node-lib/internals/events-util.d.ts.map +1 -0
  85. package/dist/node-lib/internals/index.d.ts +2 -0
  86. package/dist/node-lib/internals/index.d.ts.map +1 -0
  87. package/dist/node-lib/internals/modules.d.ts +67 -0
  88. package/dist/node-lib/internals/modules.d.ts.map +1 -0
  89. package/dist/node-lib/internals/readline-access.d.ts +2 -0
  90. package/dist/node-lib/internals/readline-access.d.ts.map +1 -0
  91. package/dist/node-lib/internals/runtime.d.ts +150 -0
  92. package/dist/node-lib/internals/runtime.d.ts.map +1 -0
  93. package/dist/node-lib/internals/util.d.ts +33 -0
  94. package/dist/node-lib/internals/util.d.ts.map +1 -0
  95. package/dist/node-lib/lazy.d.ts +31 -0
  96. package/dist/node-lib/lazy.d.ts.map +1 -0
  97. package/dist/node-lib/load.d.ts +27 -0
  98. package/dist/node-lib/load.d.ts.map +1 -0
  99. package/dist/node-lib/module-hooks.d.ts +89 -0
  100. package/dist/node-lib/module-hooks.d.ts.map +1 -0
  101. package/dist/node-lib/net-module.d.ts +181 -0
  102. package/dist/node-lib/net-module.d.ts.map +1 -0
  103. package/dist/node-lib/node-versions.d.ts +27 -0
  104. package/dist/node-lib/node-versions.d.ts.map +1 -0
  105. package/dist/node-lib/public-modules.d.ts +2 -0
  106. package/dist/node-lib/public-modules.d.ts.map +1 -0
  107. package/dist/node-lib/require-hook.d.ts +15 -0
  108. package/dist/node-lib/require-hook.d.ts.map +1 -0
  109. package/dist/node-lib/small-modules.d.ts +16 -0
  110. package/dist/node-lib/small-modules.d.ts.map +1 -0
  111. package/dist/node-lib/sources.d.ts +4 -0
  112. package/dist/node-lib/sources.d.ts.map +1 -0
  113. package/dist/node-lib/stream-module.d.ts +77 -0
  114. package/dist/node-lib/stream-module.d.ts.map +1 -0
  115. package/dist/node-lib/timers.d.ts +19 -0
  116. package/dist/node-lib/timers.d.ts.map +1 -0
  117. package/dist/node-lib/util-module.d.ts +76 -0
  118. package/dist/node-lib/util-module.d.ts.map +1 -0
  119. package/dist/node-resolution.d.ts +50 -0
  120. package/dist/node-resolution.d.ts.map +1 -0
  121. package/dist/node-resolver.d.ts +7 -0
  122. package/dist/node-resolver.d.ts.map +1 -0
  123. package/dist/npm/index.d.ts +73 -0
  124. package/dist/npm/index.d.ts.map +1 -0
  125. package/dist/npm/registry.d.ts +111 -0
  126. package/dist/npm/registry.d.ts.map +1 -0
  127. package/dist/npm/resolver.d.ts +79 -0
  128. package/dist/npm/resolver.d.ts.map +1 -0
  129. package/dist/npm/tarball.d.ts +28 -0
  130. package/dist/npm/tarball.d.ts.map +1 -0
  131. package/dist/process-tokens.d.ts +77 -0
  132. package/dist/process-tokens.d.ts.map +1 -0
  133. package/dist/punycode-source.d.ts +3 -0
  134. package/dist/punycode-source.d.ts.map +1 -0
  135. package/dist/runtime-interface.d.ts +74 -0
  136. package/dist/runtime-interface.d.ts.map +1 -0
  137. package/dist/runtime.d.ts +144 -0
  138. package/dist/runtime.d.ts.map +1 -0
  139. package/dist/server-bridge.d.ts +147 -0
  140. package/dist/server-bridge.d.ts.map +1 -0
  141. package/dist/shims/async_hooks.d.ts +50 -0
  142. package/dist/shims/async_hooks.d.ts.map +1 -0
  143. package/dist/shims/child_process.d.ts +104 -0
  144. package/dist/shims/child_process.d.ts.map +1 -0
  145. package/dist/shims/cluster.d.ts +52 -0
  146. package/dist/shims/cluster.d.ts.map +1 -0
  147. package/dist/shims/command-line.d.ts +43 -0
  148. package/dist/shims/command-line.d.ts.map +1 -0
  149. package/dist/shims/constants.d.ts +222 -0
  150. package/dist/shims/constants.d.ts.map +1 -0
  151. package/dist/shims/crypto-constants.d.ts +12 -0
  152. package/dist/shims/crypto-constants.d.ts.map +1 -0
  153. package/dist/shims/crypto.d.ts +144 -0
  154. package/dist/shims/crypto.d.ts.map +1 -0
  155. package/dist/shims/dgram.d.ts +34 -0
  156. package/dist/shims/dgram.d.ts.map +1 -0
  157. package/dist/shims/dns.d.ts +91 -0
  158. package/dist/shims/dns.d.ts.map +1 -0
  159. package/dist/shims/domain.d.ts +25 -0
  160. package/dist/shims/domain.d.ts.map +1 -0
  161. package/dist/shims/esbuild.d.ts +205 -0
  162. package/dist/shims/esbuild.d.ts.map +1 -0
  163. package/dist/shims/fs.d.ts +25 -0
  164. package/dist/shims/fs.d.ts.map +1 -0
  165. package/dist/shims/http2.d.ts +81 -0
  166. package/dist/shims/http2.d.ts.map +1 -0
  167. package/dist/shims/inspector.d.ts +25 -0
  168. package/dist/shims/inspector.d.ts.map +1 -0
  169. package/dist/shims/module.d.ts +22 -0
  170. package/dist/shims/module.d.ts.map +1 -0
  171. package/dist/shims/os.d.ts +159 -0
  172. package/dist/shims/os.d.ts.map +1 -0
  173. package/dist/shims/path.d.ts +52 -0
  174. package/dist/shims/path.d.ts.map +1 -0
  175. package/dist/shims/perf_hooks.d.ts +50 -0
  176. package/dist/shims/perf_hooks.d.ts.map +1 -0
  177. package/dist/shims/process.d.ts +226 -0
  178. package/dist/shims/process.d.ts.map +1 -0
  179. package/dist/shims/rollup.d.ts +71 -0
  180. package/dist/shims/rollup.d.ts.map +1 -0
  181. package/dist/shims/string_decoder.d.ts +41 -0
  182. package/dist/shims/string_decoder.d.ts.map +1 -0
  183. package/dist/shims/sync-child.d.ts +64 -0
  184. package/dist/shims/sync-child.d.ts.map +1 -0
  185. package/dist/shims/tls.d.ts +53 -0
  186. package/dist/shims/tls.d.ts.map +1 -0
  187. package/dist/shims/url.d.ts +84 -0
  188. package/dist/shims/url.d.ts.map +1 -0
  189. package/dist/shims/v8.d.ts +73 -0
  190. package/dist/shims/v8.d.ts.map +1 -0
  191. package/dist/shims/vfs-adapter.d.ts +126 -0
  192. package/dist/shims/vfs-adapter.d.ts.map +1 -0
  193. package/dist/shims/vm.d.ts +52 -0
  194. package/dist/shims/vm.d.ts.map +1 -0
  195. package/dist/shims/wasi.d.ts +78 -0
  196. package/dist/shims/wasi.d.ts.map +1 -0
  197. package/dist/shims/worker_threads.d.ts +66 -0
  198. package/dist/shims/worker_threads.d.ts.map +1 -0
  199. package/dist/shims/zlib-constants.d.ts +66 -0
  200. package/dist/shims/zlib-constants.d.ts.map +1 -0
  201. package/dist/shims/zlib.d.ts +102 -0
  202. package/dist/shims/zlib.d.ts.map +1 -0
  203. package/dist/transform.d.ts +24 -0
  204. package/dist/transform.d.ts.map +1 -0
  205. package/dist/types/package-json.d.ts +18 -0
  206. package/dist/types/package-json.d.ts.map +1 -0
  207. package/dist/utils/binary-encoding.d.ts +13 -0
  208. package/dist/utils/binary-encoding.d.ts.map +1 -0
  209. package/dist/utils/glob.d.ts +19 -0
  210. package/dist/utils/glob.d.ts.map +1 -0
  211. package/dist/utils/hash.d.ts +6 -0
  212. package/dist/utils/hash.d.ts.map +1 -0
  213. package/dist/virtual-fs.d.ts +259 -0
  214. package/dist/virtual-fs.d.ts.map +1 -0
  215. package/dist/vite-plugin.cjs +50 -0
  216. package/dist/vite-plugin.cjs.map +1 -0
  217. package/dist/vite-plugin.d.ts +36 -0
  218. package/dist/vite-plugin.d.ts.map +1 -0
  219. package/dist/vite-plugin.mjs +31 -0
  220. package/dist/vite-plugin.mjs.map +1 -0
  221. package/dist/worker/runtime-worker.d.ts +8 -0
  222. package/dist/worker/runtime-worker.d.ts.map +1 -0
  223. package/dist/worker-runtime.d.ts +50 -0
  224. package/dist/worker-runtime.d.ts.map +1 -0
  225. package/package.json +88 -0
  226. package/src/code-transforms.ts +560 -0
  227. package/src/config/cdn.ts +13 -0
  228. package/src/create-runtime.ts +89 -0
  229. package/src/host-globals.ts +121 -0
  230. package/src/index.ts +235 -0
  231. package/src/node-internals.ts +283 -0
  232. package/src/node-lib/LICENSE +47 -0
  233. package/src/node-lib/_http_agent.js +547 -0
  234. package/src/node-lib/_http_client.js +996 -0
  235. package/src/node-lib/_http_common.js +269 -0
  236. package/src/node-lib/_http_incoming.js +453 -0
  237. package/src/node-lib/_http_outgoing.js +1262 -0
  238. package/src/node-lib/_http_server.js +1230 -0
  239. package/src/node-lib/assert/strict.js +3 -0
  240. package/src/node-lib/assert.js +844 -0
  241. package/src/node-lib/binding/blob.ts +35 -0
  242. package/src/node-lib/binding/buffer.ts +465 -0
  243. package/src/node-lib/binding/cares_wrap.ts +57 -0
  244. package/src/node-lib/binding/fds.ts +80 -0
  245. package/src/node-lib/binding/fs.ts +857 -0
  246. package/src/node-lib/binding/handles.ts +187 -0
  247. package/src/node-lib/binding/http_parser.ts +536 -0
  248. package/src/node-lib/binding/index.ts +77 -0
  249. package/src/node-lib/binding/llhttp-wasm.ts +670 -0
  250. package/src/node-lib/binding/misc.ts +252 -0
  251. package/src/node-lib/binding/pipe_wrap.ts +166 -0
  252. package/src/node-lib/binding/process_wrap.ts +371 -0
  253. package/src/node-lib/binding/spawn_sync.ts +141 -0
  254. package/src/node-lib/binding/stream_wrap.ts +413 -0
  255. package/src/node-lib/binding/stubs.ts +19 -0
  256. package/src/node-lib/binding/tcp_wrap.ts +254 -0
  257. package/src/node-lib/binding/tty_wrap.ts +56 -0
  258. package/src/node-lib/binding/util.ts +274 -0
  259. package/src/node-lib/binding/uv.ts +102 -0
  260. package/src/node-lib/binding/zlib-pako.ts +50 -0
  261. package/src/node-lib/binding/zlib.ts +259 -0
  262. package/src/node-lib/buffer-module.ts +70 -0
  263. package/src/node-lib/buffer.js +1373 -0
  264. package/src/node-lib/child-process-module.ts +58 -0
  265. package/src/node-lib/child_process.js +1046 -0
  266. package/src/node-lib/constants.js +41 -0
  267. package/src/node-lib/diagnostics_channel.js +438 -0
  268. package/src/node-lib/events-module.ts +80 -0
  269. package/src/node-lib/events.js +1223 -0
  270. package/src/node-lib/fs-module.ts +43 -0
  271. package/src/node-lib/fs.js +3377 -0
  272. package/src/node-lib/http-bridge.ts +299 -0
  273. package/src/node-lib/http-module.ts +53 -0
  274. package/src/node-lib/http.js +198 -0
  275. package/src/node-lib/https.js +429 -0
  276. package/src/node-lib/internal/assert/assertion_error.js +417 -0
  277. package/src/node-lib/internal/assert/calltracker.js +153 -0
  278. package/src/node-lib/internal/assert/myers_diff.js +177 -0
  279. package/src/node-lib/internal/assert/utils.js +278 -0
  280. package/src/node-lib/internal/buffer.js +1120 -0
  281. package/src/node-lib/internal/child_process/serialization.js +169 -0
  282. package/src/node-lib/internal/child_process.js +1135 -0
  283. package/src/node-lib/internal/constants.js +56 -0
  284. package/src/node-lib/internal/deps/minimatch/index.js +1628 -0
  285. package/src/node-lib/internal/errors.js +1912 -0
  286. package/src/node-lib/internal/events/abort_listener.js +54 -0
  287. package/src/node-lib/internal/events/symbols.js +11 -0
  288. package/src/node-lib/internal/fixed_queue.js +119 -0
  289. package/src/node-lib/internal/freelist.js +30 -0
  290. package/src/node-lib/internal/fs/cp/cp-sync.js +242 -0
  291. package/src/node-lib/internal/fs/cp/cp.js +382 -0
  292. package/src/node-lib/internal/fs/dir.js +369 -0
  293. package/src/node-lib/internal/fs/glob.js +763 -0
  294. package/src/node-lib/internal/fs/promises.js +1312 -0
  295. package/src/node-lib/internal/fs/read/context.js +129 -0
  296. package/src/node-lib/internal/fs/recursive_watch.js +275 -0
  297. package/src/node-lib/internal/fs/rimraf.js +311 -0
  298. package/src/node-lib/internal/fs/streams.js +571 -0
  299. package/src/node-lib/internal/fs/sync_write_stream.js +49 -0
  300. package/src/node-lib/internal/fs/utils.js +987 -0
  301. package/src/node-lib/internal/fs/watchers.js +418 -0
  302. package/src/node-lib/internal/http.js +62 -0
  303. package/src/node-lib/internal/mime.js +389 -0
  304. package/src/node-lib/internal/modules/customization_hooks.js +406 -0
  305. package/src/node-lib/internal/net.js +95 -0
  306. package/src/node-lib/internal/querystring.js +119 -0
  307. package/src/node-lib/internal/readline/callbacks.js +132 -0
  308. package/src/node-lib/internal/readline/emitKeypressEvents.js +99 -0
  309. package/src/node-lib/internal/readline/interface.js +1418 -0
  310. package/src/node-lib/internal/readline/promises.js +144 -0
  311. package/src/node-lib/internal/readline/utils.js +405 -0
  312. package/src/node-lib/internal/socket_list.js +108 -0
  313. package/src/node-lib/internal/stream_base_commons.js +280 -0
  314. package/src/node-lib/internal/streams/add-abort-signal.js +60 -0
  315. package/src/node-lib/internal/streams/compose.js +257 -0
  316. package/src/node-lib/internal/streams/destroy.js +361 -0
  317. package/src/node-lib/internal/streams/duplex.js +203 -0
  318. package/src/node-lib/internal/streams/duplexify.js +379 -0
  319. package/src/node-lib/internal/streams/duplexpair.js +62 -0
  320. package/src/node-lib/internal/streams/end-of-stream.js +331 -0
  321. package/src/node-lib/internal/streams/from.js +208 -0
  322. package/src/node-lib/internal/streams/lazy_transform.js +57 -0
  323. package/src/node-lib/internal/streams/legacy.js +125 -0
  324. package/src/node-lib/internal/streams/operators.js +436 -0
  325. package/src/node-lib/internal/streams/passthrough.js +47 -0
  326. package/src/node-lib/internal/streams/pipeline.js +465 -0
  327. package/src/node-lib/internal/streams/readable.js +1762 -0
  328. package/src/node-lib/internal/streams/state.js +51 -0
  329. package/src/node-lib/internal/streams/transform.js +204 -0
  330. package/src/node-lib/internal/streams/utils.js +363 -0
  331. package/src/node-lib/internal/streams/writable.js +1163 -0
  332. package/src/node-lib/internal/tty.js +260 -0
  333. package/src/node-lib/internal/util/colors.js +40 -0
  334. package/src/node-lib/internal/util/comparisons.js +1028 -0
  335. package/src/node-lib/internal/util/debuglog.js +524 -0
  336. package/src/node-lib/internal/util/inspect.js +2535 -0
  337. package/src/node-lib/internal/util/parse_args/parse_args.js +412 -0
  338. package/src/node-lib/internal/util/parse_args/utils.js +197 -0
  339. package/src/node-lib/internal/util.js +1003 -0
  340. package/src/node-lib/internal/validators.js +654 -0
  341. package/src/node-lib/internals/addresses.ts +159 -0
  342. package/src/node-lib/internals/buffer-and-streams.ts +224 -0
  343. package/src/node-lib/internals/child-process.ts +64 -0
  344. package/src/node-lib/internals/events-util.ts +105 -0
  345. package/src/node-lib/internals/index.ts +66 -0
  346. package/src/node-lib/internals/modules.ts +117 -0
  347. package/src/node-lib/internals/readline-access.ts +16 -0
  348. package/src/node-lib/internals/runtime.ts +277 -0
  349. package/src/node-lib/internals/util.ts +98 -0
  350. package/src/node-lib/lazy.ts +158 -0
  351. package/src/node-lib/load.ts +390 -0
  352. package/src/node-lib/module-hooks.ts +281 -0
  353. package/src/node-lib/net-module.ts +248 -0
  354. package/src/node-lib/net.js +2540 -0
  355. package/src/node-lib/node-versions.ts +34 -0
  356. package/src/node-lib/os.js +356 -0
  357. package/src/node-lib/path.js +1694 -0
  358. package/src/node-lib/primordials.js +743 -0
  359. package/src/node-lib/public-modules.ts +109 -0
  360. package/src/node-lib/punycode.js +455 -0
  361. package/src/node-lib/querystring.js +484 -0
  362. package/src/node-lib/readline/promises.js +73 -0
  363. package/src/node-lib/readline.js +523 -0
  364. package/src/node-lib/require-hook.ts +27 -0
  365. package/src/node-lib/small-modules.ts +35 -0
  366. package/src/node-lib/sources.ts +200 -0
  367. package/src/node-lib/stream/promises.js +44 -0
  368. package/src/node-lib/stream-module.ts +101 -0
  369. package/src/node-lib/stream.js +152 -0
  370. package/src/node-lib/timers.ts +33 -0
  371. package/src/node-lib/tty.js +168 -0
  372. package/src/node-lib/util-module.ts +104 -0
  373. package/src/node-lib/util.js +689 -0
  374. package/src/node-lib/wasi.js +179 -0
  375. package/src/node-lib/zlib.js +1036 -0
  376. package/src/node-resolution.ts +241 -0
  377. package/src/node-resolver.ts +33 -0
  378. package/src/npm/index.ts +425 -0
  379. package/src/npm/registry.ts +235 -0
  380. package/src/npm/resolver.ts +589 -0
  381. package/src/npm/tarball.ts +246 -0
  382. package/src/process-tokens.ts +156 -0
  383. package/src/punycode-source.ts +6 -0
  384. package/src/runtime-interface.ts +85 -0
  385. package/src/runtime.ts +3073 -0
  386. package/src/server-bridge.ts +971 -0
  387. package/src/shims/async_hooks.ts +181 -0
  388. package/src/shims/child_process.ts +1591 -0
  389. package/src/shims/cluster.ts +79 -0
  390. package/src/shims/command-line.ts +79 -0
  391. package/src/shims/constants.ts +68 -0
  392. package/src/shims/crypto-constants.ts +11 -0
  393. package/src/shims/crypto.ts +1061 -0
  394. package/src/shims/dgram.ts +47 -0
  395. package/src/shims/dns.ts +185 -0
  396. package/src/shims/domain.ts +58 -0
  397. package/src/shims/esbuild.ts +1638 -0
  398. package/src/shims/fs.ts +82 -0
  399. package/src/shims/http2.ts +96 -0
  400. package/src/shims/inspector.ts +30 -0
  401. package/src/shims/module.ts +86 -0
  402. package/src/shims/os.ts +195 -0
  403. package/src/shims/path.ts +246 -0
  404. package/src/shims/perf_hooks.ts +92 -0
  405. package/src/shims/process.ts +804 -0
  406. package/src/shims/rollup.ts +149 -0
  407. package/src/shims/string_decoder.ts +396 -0
  408. package/src/shims/sync-child.ts +469 -0
  409. package/src/shims/tls.ts +94 -0
  410. package/src/shims/url.ts +217 -0
  411. package/src/shims/v8.ts +113 -0
  412. package/src/shims/vfs-adapter.ts +408 -0
  413. package/src/shims/vm.ts +132 -0
  414. package/src/shims/wasi.ts +1440 -0
  415. package/src/shims/worker_threads.ts +114 -0
  416. package/src/shims/zlib-constants.ts +65 -0
  417. package/src/shims/zlib.ts +228 -0
  418. package/src/transform.ts +324 -0
  419. package/src/types/external.d.ts +55 -0
  420. package/src/types/package-json.ts +17 -0
  421. package/src/types/pako-zlib.d.ts +30 -0
  422. package/src/types/sha.js.d.ts +14 -0
  423. package/src/utils/binary-encoding.ts +43 -0
  424. package/src/utils/glob.ts +54 -0
  425. package/src/utils/hash.ts +12 -0
  426. package/src/virtual-fs.ts +1048 -0
  427. package/src/vite-plugin.ts +76 -0
  428. package/src/worker/runtime-worker.ts +128 -0
  429. package/src/worker-runtime.ts +145 -0
@@ -0,0 +1,1591 @@
1
+ /**
2
+ * The engine's process model: what a run of a program IS here.
3
+ *
4
+ * `child_process` itself is Node's own file now, vendored in
5
+ * `src/node-lib/child_process.js` and loaded over the binding in
6
+ * `src/node-lib/binding/`. What is left here is everything that file does not
7
+ * own and cannot know: the `node` command -- a guest program running as a run
8
+ * of the engine, with its own process, its own timers, its own uncaught
9
+ * exceptions and its own idea of when it is done -- the routing of a command
10
+ * line to the host's process host or to the engine's shell, the streams and
11
+ * the name a host gives one run, and the rule that decides a quiet program is
12
+ * finished.
13
+ *
14
+ * `Process.spawn` in the binding reaches all of it through the one door
15
+ * installed at the bottom of `initChildProcess`.
16
+ */
17
+
18
+ // Polyfill process for just-bash (it expects Node.js environment)
19
+ if (typeof globalThis.process === 'undefined') {
20
+ (globalThis as any).process = {
21
+ env: {
22
+ HOME: '/home/user',
23
+ USER: 'user',
24
+ PATH: '/usr/local/bin:/usr/bin:/bin',
25
+ },
26
+ cwd: () => '/',
27
+ platform: 'linux',
28
+ version: 'v18.0.0',
29
+ versions: { node: '18.0.0' },
30
+ stdout: { write: () => {} },
31
+ stderr: { write: () => {} },
32
+ };
33
+ }
34
+
35
+ import { forGuestRealm, heldWork } from '../host-globals';
36
+ import { Bash, defineCommand } from 'just-bash';
37
+ import type { CommandContext, ExecResult as JustBashExecResult } from 'just-bash';
38
+ import { EventEmitter } from '../node-lib/events-module';
39
+ import { __substrateExitCode, __substrateUncaughtCapture } from './process';
40
+ import { Buffer } from '../node-lib/buffer-module';
41
+ import type { VirtualFS } from '../virtual-fs';
42
+ import { VirtualFSAdapter } from './vfs-adapter';
43
+ import { __releaseOwnedServers, __ownedServerPorts } from '../node-lib/net-module';
44
+ import { __ownedHandleCount, __releaseOwnedHandles } from '../node-lib/net-module';
45
+ import { setProcessRunner, type RunRequest, type StartedRun } from '../node-lib/binding/process_wrap';
46
+ import { Pipe, constants as pipeConstants } from '../node-lib/binding/pipe_wrap';
47
+ import { registerRunFd, releaseRunFds } from '../node-lib/binding/fds';
48
+ import { UV_ESRCH } from '../node-lib/binding/uv';
49
+ import { setupChannel } from '../node-lib/child-process-module';
50
+ import { getCommandNames } from 'just-bash';
51
+ import { diagnosticsChannelModule } from '../node-lib/small-modules';
52
+ import { __substrateExecPath, __substrateProgramName, __substrateLineFor, __substrateShellLine, setProgramResolver } from './command-line';
53
+
54
+ import { __substrateChildren, __onUncaughtException, __reportUncaughtException } from './process';
55
+
56
+ import { PROCESS_TOKEN_ENV, __recordRun, __runFor, __currentProcessToken, __lastLaunchedToken, __setLastLaunchedToken, __stopOwnedProcess, enterRun, mintPid, setRunPid, runPid, forgetRunPid, type ProcessToken } from '../process-tokens';
57
+ /** The host's own `process`, where it has one that emits, taken as the shim loads and before any guest's takes the global name. */
58
+ const __hostProcess: { on(event: string, listener: (reason: unknown) => void): unknown; off(event: string, listener: (reason: unknown) => void): unknown } | null =
59
+ typeof process !== 'undefined' && process !== null && typeof (process as { on?: unknown }).on === 'function' && typeof (process as { off?: unknown }).off === 'function'
60
+ ? (process as { on(event: string, listener: (reason: unknown) => void): unknown; off(event: string, listener: (reason: unknown) => void): unknown })
61
+ : null;
62
+ import { Runtime, pendingGuestTimers, stopGuestTimers, __substratePendingOf } from '../runtime';
63
+ import { __nodeResolverFor } from '../node-resolver';
64
+ import { resolve as __resolvePath } from './path';
65
+ import { setSyncChildVfs, warmSyncChild } from './sync-child';
66
+ import type { PackageJson } from '../types/package-json';
67
+
68
+ // Singleton bash instance - uses VFS adapter for two-way file sync
69
+ let bashInstance: Bash | null = null;
70
+ let vfsAdapter: VirtualFSAdapter | null = null;
71
+ let currentVfs: VirtualFS | null = null;
72
+ /**
73
+ * One shell per tree. Two `createContainer` calls in one realm used to share
74
+ * this module's `currentVfs` and `bashInstance`, so the first container's
75
+ * `node /main.cjs` ran on the second's tree and printed SECOND. The first
76
+ * tree still occupies `currentVfs` / `bashInstance` until a second arrives.
77
+ */
78
+ const shells = new WeakMap<VirtualFS, { bash: Bash; adapter: VirtualFSAdapter }>();
79
+ let processRunnerInstalled = false;
80
+
81
+ /** The tree of the guest whose code is executing, else the last tree given to the engine. */
82
+ function treeForRun(): VirtualFS | null {
83
+ // Same symbol Runtime hangs the tree on; named here so this file does not
84
+ // import the fs binding (that import put constantsBinding in the
85
+ // consumer bundle's cycle with crypto).
86
+ const realm = globalThis as unknown as { process?: Record<symbol, unknown> };
87
+ const found = realm.process?.[Symbol.for('tabnode.run.vfs')] as VirtualFS | undefined;
88
+ return found ?? currentVfs;
89
+ }
90
+
91
+ function shellOf(tree: VirtualFS | null | undefined): { bash: Bash; adapter: VirtualFSAdapter } | null {
92
+ if (tree) {
93
+ const owned = shells.get(tree);
94
+ if (owned) return owned;
95
+ }
96
+ return bashInstance && vfsAdapter ? { bash: bashInstance, adapter: vfsAdapter } : null;
97
+ }
98
+
99
+ // Track active forked child processes so the node command can detect when children exit.
100
+ // When the last child exits, the node command uses a shorter idle timeout.
101
+ let _activeForkedChildren = 0;
102
+ let _onForkedChildExit: (() => void) | null = null;
103
+
104
+ // Patch Object.defineProperty globally to force configurable: true on globalThis properties.
105
+ // In real Node.js, each process has its own globalThis. In our browser environment,
106
+ // all forks share globalThis, so libraries like vitest that define non-configurable
107
+ // properties (e.g. __vitest_index__) need them to be configurable for re-runs.
108
+ const _realDefineProperty = Object.defineProperty;
109
+ Object.defineProperty = function(target: object, key: PropertyKey, descriptor: PropertyDescriptor): object {
110
+ if (target === globalThis && descriptor && !descriptor.configurable) {
111
+ descriptor = { ...descriptor, configurable: true };
112
+ }
113
+ return _realDefineProperty.call(Object, target, key, descriptor) as object;
114
+ } as typeof Object.defineProperty;
115
+
116
+ /** The stdin of a held run's guest, as the run's own. */
117
+ type RunStdin = { emit: (event: string, ...args: unknown[]) => void; push: (chunk: string | Uint8Array | null) => boolean };
118
+
119
+ /**
120
+ * What a host gave one run: where its output goes, the handle that aborts it,
121
+ * whether the host holds it open, and — while it lasts — its guest's stdin.
122
+ *
123
+ * This is per run and never a module global. A child a guest spawns is a
124
+ * second run on the same engine, so a global here meant the later run took the
125
+ * earlier one's stream, its signal and its stdin: a trivial child was read as
126
+ * a watch (it arrived with an abort handle) and never ended, and its parent's
127
+ * output went down the child's channel.
128
+ */
129
+ export interface RunStreams {
130
+ onStdout?: (data: string) => void;
131
+ onStderr?: (data: string) => void;
132
+ signal?: AbortSignal;
133
+ /** The host keeps this run open; a run that is not held ends when its loop has nothing left. */
134
+ held: boolean;
135
+ /**
136
+ * The run's fd 0 has a writer that has not closed it. A held run's prompt is
137
+ * one; a spawned child whose `stdio[0]` is a pipe is the other, and its
138
+ * writer is the parent, which may write long after the child has started.
139
+ * Without it the child's standard input ended before the first write.
140
+ */
141
+ stdinOpen?: boolean;
142
+ stdin?: RunStdin | null;
143
+ /**
144
+ * The run's fd 2 is a pipe the parent holds, not the host's own stdio and
145
+ * not inherit. A run that ends uncaught or by `process.exit` with a
146
+ * nonzero code on that pipe writes one line to the host realm's console.
147
+ */
148
+ stderrIsPipe?: boolean;
149
+ }
150
+
151
+ /**
152
+ * The runs in flight, in the order they started. `container.run` registers one
153
+ * before exec and releases it when the run ends.
154
+ */
155
+ const _runStreams = new Map<ProcessToken, RunStreams>();
156
+
157
+ /**
158
+ * One line on the host realm's console when a run the tab owns dies and
159
+ * nobody is reading its stderr.
160
+ *
161
+ * Node prints an uncaught exception to that process's stderr. A parent that
162
+ * pipes the fd and drops it — openvscode-server pipes the extension host
163
+ * into `@vscode/spdlog`, a native module that does not load — loses the
164
+ * reason, in Node too. The child's stderr still goes to its parent; this
165
+ * is the one extra line the host realm can say, because the tab owns the
166
+ * run. Inherit and the host's own stdio already show the reason and get
167
+ * no duplicate. Article 6.
168
+ */
169
+ function writePipedEndReceipt(
170
+ pid: number,
171
+ argv0: string,
172
+ firstArg: string,
173
+ kind: 'uncaught' | number,
174
+ error: unknown,
175
+ ): void {
176
+ const who = firstArg.length > 0 ? `${argv0} ${firstArg}` : argv0;
177
+ const ended = kind === 'uncaught' ? 'uncaught' : String(kind);
178
+ let detail = '';
179
+ if (error instanceof Error) {
180
+ const stack = error.stack ?? `${error.name}: ${error.message}`;
181
+ const lines = stack.split('\n');
182
+ const head = (lines[0] ?? '').trimEnd();
183
+ let frame = '';
184
+ for (let i = 1; i < lines.length; i += 1) {
185
+ const line = lines[i]!.trim();
186
+ if (line.startsWith('at ')) { frame = line; break; }
187
+ }
188
+ detail = frame.length > 0 ? `${head} ${frame}` : head;
189
+ } else if (error !== undefined && error !== null) {
190
+ detail = String(error).split('\n')[0] ?? '';
191
+ }
192
+ console.error(`[run ${pid}] ${who} ended ${ended}${detail.length > 0 ? `: ${detail}` : ''}`);
193
+ }
194
+
195
+ /** Register what the host gave this run, under the run's own name. */
196
+ export function registerRunStreams(token: ProcessToken, streams: RunStreams): void {
197
+ _runStreams.delete(token);
198
+ _runStreams.set(token, streams);
199
+ }
200
+
201
+ /** Forget a run that has ended. */
202
+ export function releaseRunStreams(token: ProcessToken): void {
203
+ _runStreams.delete(token);
204
+ }
205
+
206
+ /** What the host gave the named run, while it lasts. */
207
+ function runStreamsFor(token: ProcessToken): RunStreams | undefined {
208
+ return _runStreams.get(token);
209
+ }
210
+
211
+ /**
212
+ * Send data to the stdin of one run's guest process.
213
+ * Emits both 'data' and 'keypress' events (vitest uses readline keypress events).
214
+ *
215
+ * With a token, that run's stdin. Without one — the door a host that has no id
216
+ * to give still has — the most recently started run the host holds, which is
217
+ * the prompt a person is typing to; a run nobody holds has no prompt.
218
+ */
219
+ export function sendStdin(data: string, token?: ProcessToken): void {
220
+ let target: RunStdin | null | undefined;
221
+ if (typeof token === 'string') {
222
+ target = _runStreams.get(token)?.stdin;
223
+ } else {
224
+ // Insertion order is start order, so the last held run is the last match.
225
+ for (const streams of _runStreams.values()) {
226
+ if (streams.held && streams.stdin) target = streams.stdin;
227
+ }
228
+ }
229
+ if (target) {
230
+ // Bytes on the guest's fd 0 go through `push`, the way a pipe's arriving
231
+ // bytes reach a Node stream: a reader in paused mode, an iterator, or a
232
+ // `pipe` sees them, where a bare `emit('data')` reached only a listener
233
+ // that happened to be in flowing mode.
234
+ target.push(data);
235
+ for (const ch of data) {
236
+ target.emit('keypress', ch, {
237
+ sequence: ch,
238
+ name: ch,
239
+ ctrl: false,
240
+ meta: false,
241
+ shift: false,
242
+ });
243
+ }
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Initialize the child_process shim with a VirtualFS instance
249
+ * Creates a single Bash instance with VirtualFSAdapter for efficient file access
250
+ */
251
+ /**
252
+ * The last resort, the way Node's `process._fatalException` is one.
253
+ *
254
+ * A guest's callbacks are entered from the realm's own loop through more doors
255
+ * than the engine wraps — a `setImmediate`, a microtask, a host callback — and
256
+ * an exception out of one of those reaches the realm's global `error` event.
257
+ * In the tab that realm is a worker, and the substrate's container reads a
258
+ * worker error as a dead host and disposes it: one program's throw ended every
259
+ * program in the tab. Such an error still belongs to the program that was
260
+ * running, so it is reported as that program's, and the event is cancelled so
261
+ * the page's `Worker.onerror` never fires. An error with no program to own it
262
+ * is left alone, so the host's own failure reporting still sees it.
263
+ *
264
+ * Only a realm that exists to run guests gets this: registered for the guest
265
+ * realm, and installed in a worker. In a Node host the engine is a library and
266
+ * the host reports its own failures.
267
+ */
268
+ let __backstopInstalled = false;
269
+ forGuestRealm(() => {
270
+ if (__backstopInstalled) return;
271
+ if (typeof (globalThis as Record<string, unknown>).WorkerGlobalScope === 'undefined') return;
272
+ if (typeof globalThis.addEventListener !== 'function') return;
273
+ __backstopInstalled = true;
274
+ globalThis.addEventListener('error', (event: Event) => {
275
+ const token = __currentProcessToken() ?? __lastLaunchedToken;
276
+ if (token === null) return;
277
+ const raised = event as ErrorEvent;
278
+ const error = raised.error ?? new Error(raised.message || 'uncaught exception');
279
+ if (__runFor(token)?.reportUncaught(error)) event.preventDefault();
280
+ });
281
+ });
282
+
283
+ export function initChildProcess(vfs: VirtualFS): void {
284
+ const existing = shells.get(vfs);
285
+ if (existing) {
286
+ currentVfs = vfs;
287
+ bashInstance = existing.bash;
288
+ vfsAdapter = existing.adapter;
289
+ setSyncChildVfs(vfs);
290
+ return;
291
+ }
292
+ // This tree's own shell. The `node` and `npm` commands close over it, so a
293
+ // later `initChildProcess` of another tree cannot redirect them.
294
+ const tree = vfs;
295
+ currentVfs = tree;
296
+ // A synchronous child runs on a thread over this same tree. In a browser
297
+ // realm the thread cannot start inside the synchronous call that needs it —
298
+ // a worker is started by the realm's own loop — so it is warmed here, while
299
+ // the loop still runs. A Node host starts its threads itself, and is not
300
+ // given one it may never use.
301
+ setSyncChildVfs(tree);
302
+ // Node's `process.execPath` names a program that exists. Every tool that
303
+ // re-runs itself, and every one of Node's own tests that spawns a child,
304
+ // writes the path rather than the name — `execSync('"' + process.execPath +
305
+ // '" file')` — and the engine's shell answered "No such file or directory",
306
+ // because its node was a command with no file anywhere. The engine's node is
307
+ // placed where its execPath says it is: a line of shell that runs the
308
+ // command under its plain name.
309
+ try {
310
+ if (!tree.existsSync(__substrateExecPath)) {
311
+ tree.mkdirSync(__substrateExecPath.slice(0, __substrateExecPath.lastIndexOf('/')), { recursive: true });
312
+ tree.writeFileSync(__substrateExecPath, 'node "$@"\n');
313
+ }
314
+ } catch { /* a tree that refuses the write keeps the command under its plain name */ }
315
+ if (typeof (globalThis as Record<string, unknown>).WorkerGlobalScope !== 'undefined' || typeof (globalThis as Record<string, unknown>).document !== 'undefined') warmSyncChild();
316
+ vfsAdapter = new VirtualFSAdapter(tree);
317
+
318
+ // Create custom 'node' command that runs JS files using the Runtime
319
+ /**
320
+ * Hears a realm's unhandled promise rejections for the length of a run and
321
+ * answers a detach. A browser realm raises `unhandledrejection` on its
322
+ * global (the default, a console line, is prevented: the run reports it);
323
+ * a Node host has no such event and reports through its own `process`,
324
+ * the one the host had before any guest's took the global name.
325
+ *
326
+ * Both surfaces carry the promise that rejected, and the report is given it:
327
+ * Node's `unhandledRejection` listener is called `(reason, promise)`, and a
328
+ * listener that uses its second argument is ordinary. openvscode-server's
329
+ * own (`out/server-main.js`) keeps the promise and calls `promise.catch` a
330
+ * second later; under an engine that handed it `undefined` every rejection
331
+ * of the server's became `TypeError: Cannot read properties of undefined
332
+ * (reading 'catch')` inside a timer, 59 of them in one boot.
333
+ */
334
+ const __listenForUnhandledRejections = (report: (reason: unknown, promise?: Promise<unknown>) => void): (() => void) => {
335
+ if (typeof globalThis.addEventListener === 'function') {
336
+ const target = globalThis as unknown as EventTarget;
337
+ const listener = (event: Event) => { event.preventDefault(); report((event as PromiseRejectionEvent).reason, (event as PromiseRejectionEvent).promise); };
338
+ target.addEventListener('unhandledrejection', listener);
339
+ return () => target.removeEventListener('unhandledrejection', listener);
340
+ }
341
+ const host = __hostProcess;
342
+ if (host) {
343
+ const listener = (reason: unknown, promise?: Promise<unknown>) => report(reason, promise);
344
+ host.on('unhandledRejection', listener);
345
+ return () => host.off('unhandledRejection', listener);
346
+ }
347
+ return () => {};
348
+ };
349
+ const nodeCommand = defineCommand('node', async (args, ctx) => {
350
+ if (!tree) {
351
+ return { stdout: '', stderr: 'VFS not initialized\n', exitCode: 1 };
352
+ }
353
+
354
+ // Node reads its own options before the script: `node --turbo-fast-api-calls
355
+ // file.js a b` runs file.js with `a b`, and the options it were given are
356
+ // its `execArgv`. Node's own suite spawns children that way, and the
357
+ // engine took the first option for the script and died on
358
+ // "Cannot find module '--turbo-fast-api-calls'".
359
+ //
360
+ // An option may also take its value as the next word, and the engine
361
+ // cannot know which do: `node --conditions node child.js` gave `node` as
362
+ // the script. So the script is the first argument that NAMES A FILE, and
363
+ // everything before it is `execArgv` -- which is what Node's own option
364
+ // parser arrives at, by knowing its options rather than by looking. A
365
+ // command line that names no file at all keeps the first word after the
366
+ // options, so `node missing.js` still says which module it cannot find.
367
+ //
368
+ // The path on a `node` command line is a path, and it is resolved as
369
+ // `require` resolves one: the file itself, then `.js`, `.mjs`, `.cjs`,
370
+ // `.json`, then a directory's `package.json` `main` or its `index.js`.
371
+ // `fork` used to do this for itself; `fork` is Node's own file now and
372
+ // hands the module path straight to `node`, so the resolution belongs
373
+ // where a path on a command line is read. Opened literally, the path
374
+ // openvscode-server forks its pty host with, `<server>/out/bootstrap-fork`
375
+ // for the file `bootstrap-fork.js`, was ENOENT and the pty host died at
376
+ // its first fork.
377
+ const execArgv: string[] = [];
378
+ let first = 0;
379
+ while (first < args.length && args[first]!.startsWith('-') && args[first] !== '-' && args[first] !== '--') {
380
+ execArgv.push(args[first]!);
381
+ first += 1;
382
+ }
383
+ if (args[first] === '--') first += 1;
384
+ const resolver = __nodeResolverFor(tree, 'runtime');
385
+ const fileNamed = (word: string): string | null => {
386
+ const requested = __resolvePath(ctx.cwd, word);
387
+ const found = resolver.resolve(requested, ctx.cwd);
388
+ if (found !== null && tree.existsSync(found)) return found;
389
+ return tree.existsSync(requested) ? requested : null;
390
+ };
391
+ let script = first;
392
+ let resolvedPath: string | null = null;
393
+ for (let index = first; index < args.length; index += 1) {
394
+ const found = fileNamed(args[index]!);
395
+ if (found !== null) { script = index; resolvedPath = found; break; }
396
+ }
397
+ for (let index = first; index < script; index += 1) execArgv.push(args[index]!);
398
+ if (!args[script]) {
399
+ return { stdout: '', stderr: 'Usage: node <script.js> [args...]\n', exitCode: 1 };
400
+ }
401
+ if (resolvedPath === null) {
402
+ return { stdout: '', stderr: `Error: Cannot find module '${__resolvePath(ctx.cwd, args[script]!)}'\n`, exitCode: 1 };
403
+ }
404
+
405
+ let stdout = '';
406
+ let stderr = '';
407
+
408
+ // A run the host named is this guest process, for as long as the run
409
+ // lasts: `pendingTimers`, `processPorts` and `stopProcess` are answered
410
+ // from here, and a server the guest opens registers under this name.
411
+ const runToken = runTokenOf(ctx);
412
+ // What the host gave THIS run — its streams, its abort handle, whether it
413
+ // holds the run open. Read once here and nowhere from a module global, so
414
+ // a child spawned by this guest (a second run) cannot take them.
415
+ const streams = runToken === null ? undefined : runStreamsFor(runToken);
416
+
417
+ // Track whether process.exit() was called
418
+ let exitCalled = false;
419
+ let exitCode = 0;
420
+ let syncExecution = true;
421
+ let exitResolve: ((code: number) => void) | null = null;
422
+ const exitPromise = new Promise<number>((resolve) => { exitResolve = resolve; });
423
+
424
+ // Helper to append to stdout, also streaming if configured
425
+ // A process that has exited writes nothing more, as Node's cannot: a
426
+ // continuation of the guest that runs on after its exit keeps its output
427
+ // to itself.
428
+ const appendStdout = (data: string) => {
429
+ if (exitCalled) return;
430
+ stdout += data;
431
+ streams?.onStdout?.(data);
432
+ };
433
+ const appendStderr = (data: string) => {
434
+ if (exitCalled) return;
435
+ stderr += data;
436
+ streams?.onStderr?.(data);
437
+ };
438
+
439
+ // A child started with an IPC channel is told its descriptor in its
440
+ // environment, exactly as Node tells one; the variables are taken out
441
+ // before the guest sees them, as Node's own bootstrap deletes them, so a
442
+ // grandchild does not inherit a channel that is not its own.
443
+ const guestEnv = guestEnvironmentOf(ctx);
444
+ const channelFd = Number.parseInt(guestEnv.NODE_CHANNEL_FD ?? '', 10);
445
+ const channelSerialization = guestEnv.NODE_CHANNEL_SERIALIZATION_MODE || 'json';
446
+ delete guestEnv.NODE_CHANNEL_FD;
447
+ delete guestEnv.NODE_CHANNEL_SERIALIZATION_MODE;
448
+
449
+ // Create a runtime with output capture for both console.log AND process.stdout.write
450
+ const runtime = new Runtime(tree, {
451
+ cwd: ctx.cwd,
452
+ env: guestEnv,
453
+ onConsole: (method, consoleArgs) => {
454
+ const msg = consoleArgs.map(a => String(a)).join(' ') + '\n';
455
+ if (method === 'error') {
456
+ appendStderr(msg);
457
+ } else {
458
+ appendStdout(msg);
459
+ }
460
+ },
461
+ onStdout: (data: string) => {
462
+ appendStdout(data);
463
+ },
464
+ onStderr: (data: string) => {
465
+ appendStderr(data);
466
+ },
467
+ // The guest's standard input is what the shell put on its fd 0: the text
468
+ // left of a pipe, or the `stdin` a host gave `container.run`. A held run
469
+ // (one the host streams and can still feed with `sendStdin`) leaves it
470
+ // open, as a pipe whose writer has not closed.
471
+ stdin: typeof ctx.stdin === 'string' ? ctx.stdin : '',
472
+ ...(streams?.held || streams?.stdinOpen ? { stdinHeld: true } : {}),
473
+ ...(streams?.held ? { tty: true } : {}),
474
+ // The numbers this run was started with, so the guest's `process.pid`
475
+ // is the one its parent's handle carries.
476
+ ...(runPid(runToken) ? { pid: runPid(runToken)!.pid, ppid: runPid(runToken)!.ppid } : {}),
477
+ });
478
+
479
+ // Override process.exit to resolve the completion promise
480
+ const proc = runtime.getProcess();
481
+ // This run's numbers, recorded under its name: a child it spawns reads
482
+ // them for its own `ppid`, and `process.kill(pid, 0)` asks this registry
483
+ // whether a pid is a live process.
484
+ if (runToken !== null) setRunPid(runToken, proc.pid, proc.ppid);
485
+ const releaseRun = runToken === null ? null : __recordRun(runToken, {
486
+ pendingTimers: () => pendingGuestTimers(proc),
487
+ stopTimers: () => stopGuestTimers(proc),
488
+ reportUncaught: (error: unknown) => __reportUncaughtException(proc, error),
489
+ });
490
+ const launchedBefore = __lastLaunchedToken;
491
+ if (runToken !== null) __setLastLaunchedToken(runToken);
492
+ let hostReceiptWritten = false;
493
+ let lastUncaught: unknown;
494
+ const writeHostReceipt = (kind: 'uncaught' | number, error?: unknown): void => {
495
+ if (hostReceiptWritten || streams?.stderrIsPipe !== true) return;
496
+ hostReceiptWritten = true;
497
+ writePipedEndReceipt(proc.pid, proc.argv0 || 'node', proc.argv[1] ?? '', kind, error);
498
+ };
499
+ proc.exit = ((code = 0) => {
500
+ if (!exitCalled) {
501
+ exitCalled = true;
502
+ // As Node takes one: a string from a command line becomes its number.
503
+ exitCode = __substrateExitCode(code);
504
+ code = exitCode;
505
+ // A handled uncaught that then `process.exit(1)` is a normal exit
506
+ // with code 1. The receipt still names the error the handler saw:
507
+ // VS Code's host installs `uncaughtException` and exits 1, and the
508
+ // previous line was `ended 1` with no reason.
509
+ if (exitCode !== 0) writeHostReceipt(exitCode, lastUncaught);
510
+ // Node runs a program's `exit` listeners while everything it holds is
511
+ // still open, and closes the loop's handles after them. Releasing
512
+ // first closed a forked child's IPC channel before its own exit
513
+ // listeners ran, and VS Code's file-watcher child -- which pipes its
514
+ // console over `process.send` and dies of a failed require -- wrote on
515
+ // the closed channel and took `write EBADF` as its last act.
516
+ proc.emit('exit', code);
517
+ exitResolve!(code);
518
+ }
519
+ // `process.exit()` ends a Node process and everything it holds; a named
520
+ // run's servers and timers go with it.
521
+ if (runToken !== null) { __releaseOwnedServers(runToken); __releaseOwnedHandles(runToken); }
522
+ // In sync context, throw to stop execution (like real process.exit)
523
+ // In async context, return silently to avoid unhandled rejections
524
+ if (syncExecution) {
525
+ throw new Error(`Process exited with code ${code}`);
526
+ }
527
+ }) as (code?: number) => never;
528
+
529
+ // Set up process.argv for the script. Node fills argv[0] with the
530
+ // executable's path, the same value `process.execPath` reports; `argv0`
531
+ // keeps the original argv[0], the plain word.
532
+ proc.argv = [__substrateExecPath, resolvedPath, ...args.slice(script + 1)];
533
+ proc.argv0 = 'node';
534
+ proc.execArgv = execArgv;
535
+
536
+ // Whoever writes to this run's fd 0 writes here: a person typing at a held
537
+ // run's prompt, through `sendStdin`, or a parent writing to the stdin pipe
538
+ // of a child it spawned.
539
+ if (streams) streams.stdin = proc.stdin;
540
+
541
+ // For long-running commands (watch mode), report as TTY so tools like
542
+ // vitest set up interactive features (file watching, stdin commands). A
543
+ // spawned child's stdio is a pipe, as Node's is, and gets none of this.
544
+ if (streams?.held) {
545
+ proc.stdout.isTTY = true;
546
+ proc.stderr.isTTY = true;
547
+ proc.stdin.isTTY = true;
548
+ proc.stdin.setRawMode = () => proc.stdin;
549
+ }
550
+
551
+ // A child started with a channel wires its own end of it before its
552
+ // module runs, which is what `lib/internal/process/pre_execution.js` does
553
+ // in Node and `_forkChild` is the body of. Inside the run, so the pipe is
554
+ // this run's handle and the descriptor is read against this run's table.
555
+ if (Number.isInteger(channelFd) && channelFd >= 0 && runToken !== null) {
556
+ enterRun(runToken, () => { attachChannel(proc, channelFd, channelSerialization); });
557
+ }
558
+
559
+ // A promise rejection nobody handles ends the program, as it ends one in
560
+ // Node (since 15 an unhandled rejection is an uncaught exception): a guest
561
+ // listener for `unhandledRejection` on its process takes it, else one for
562
+ // `uncaughtException` with origin 'unhandledRejection', else the error is
563
+ // printed to stderr and the program exits 1. It was reported only while
564
+ // the runner waited on a quiet program, only through a realm's
565
+ // `unhandledrejection` event (a Node host has none; it reports through
566
+ // `process`), and never ended the program: vue-pure-admin's mock loader
567
+ // died in a rejection and the program ended silently with exit 0, where
568
+ // Node prints the error and exits 1. The report is attached for the whole
569
+ // run, on whichever surface the realm has.
570
+ const onUnhandledRejection = (reason: unknown, promise?: Promise<unknown>): void => {
571
+ if (reason instanceof Error && reason.message.startsWith('Process exited with code')) return;
572
+ lastUncaught = reason;
573
+ if (proc.listenerCount('unhandledRejection') > 0) { proc.emit('unhandledRejection', reason, promise); return; }
574
+ if (proc.listenerCount('uncaughtException') > 0) { proc.emit('uncaughtException', reason, 'unhandledRejection'); return; }
575
+ const errorMsg = reason instanceof Error ? `${reason.message}\n${reason.stack || ''}` : String(reason);
576
+ appendStderr(`Error: ${errorMsg}\n`);
577
+ writeHostReceipt('uncaught', reason);
578
+ if (exitCalled) return;
579
+ const wasSync = syncExecution;
580
+ syncExecution = false;
581
+ try { proc.exit(1); } finally { syncExecution = wasSync; }
582
+ };
583
+ const detachRejections = __listenForUnhandledRejections(onUnhandledRejection);
584
+
585
+ // An exception nobody caught ends the program it was thrown in, as it ends
586
+ // one in Node: a guest listener for `uncaughtException` on its process
587
+ // takes it, else the stack is printed to this program's stderr and the
588
+ // program exits 1. Nothing of it reaches the realm. Unreported, a throw
589
+ // from a timer callback of openvscode-server's reached the worker's global
590
+ // `error` event, the substrate's container read that as a dead host and
591
+ // disposed the worker, and every run's writes failed from then on.
592
+ const onUncaughtException = (error: unknown): void => {
593
+ if (error instanceof Error && error.message.startsWith('Process exited with code')) return;
594
+ lastUncaught = error;
595
+ // A program that set a capture callback takes every uncaught exception
596
+ // itself, before any listener, which is Node's own order.
597
+ const capture = __substrateUncaughtCapture();
598
+ if (capture) { capture(error); return; }
599
+ if (proc.listenerCount('uncaughtException') > 0) { proc.emit('uncaughtException', error, 'uncaughtException'); return; }
600
+ appendStderr(`${error instanceof Error ? (error.stack ?? `${error.name}: ${error.message}`) : String(error)}\n`);
601
+ writeHostReceipt('uncaught', error);
602
+ if (exitCalled) return;
603
+ const wasSync = syncExecution;
604
+ syncExecution = false;
605
+ try { proc.exit(1); } finally { syncExecution = wasSync; }
606
+ };
607
+ const detachUncaught = __onUncaughtException(proc, onUncaughtException);
608
+ try {
609
+
610
+ let entrySettling: Promise<unknown> | undefined;
611
+ try {
612
+ // Run the script (synchronous part)
613
+ // The entry runs AS this run: the token a `spawn` inside it reads is
614
+ // this program's, not whichever run started last. The engine's storage
615
+ // carries it into the timers, microtasks and `then` callbacks the entry
616
+ // schedules from here, so a child spawned later still names its parent.
617
+ const runEntry = () => runtime.runFile(resolvedPath);
618
+ entrySettling = __substratePendingOf(
619
+ (runToken === null ? runEntry() : enterRun(runToken, runEntry)).exports,
620
+ );
621
+ } catch (error) {
622
+ // process.exit() throws to stop sync execution — this is expected
623
+ if (error instanceof Error && error.message.startsWith('Process exited with code')) {
624
+ return { stdout, stderr, exitCode };
625
+ }
626
+ // A throw out of the entry is this program's uncaught exception and
627
+ // goes through the same door as any other: the guest's own
628
+ // `uncaughtException` listeners, else its stderr and exit 1 -- which
629
+ // runs its `exit` listeners. Returning a result straight from here
630
+ // skipped them, and VS Code's file-watcher child, which pipes its
631
+ // console over IPC and dies of a failed require, never sent the lines
632
+ // that say why.
633
+ syncExecution = false;
634
+ onUncaughtException(error);
635
+ return { stdout, stderr, exitCode: exitCalled ? exitCode : 1 };
636
+ } finally {
637
+ // After runFile returns, switch to async mode (no more throwing from process.exit)
638
+ syncExecution = false;
639
+ }
640
+
641
+ // If process.exit was called synchronously (but didn't throw for some reason), return
642
+ if (exitCalled) {
643
+ return { stdout, stderr, exitCode };
644
+ }
645
+
646
+ // An entry still settling, a top-level `await` in it or in what it
647
+ // imports, has run when it has settled; one that fails there fails the
648
+ // program, as Node prints the error and exits 1.
649
+ // The program may exit before the entry settles, by `process.exit` or a
650
+ // fatal rejection in the meantime; the run ends at the exit, as Node's
651
+ // does, and what the entry does after is its own.
652
+ if (entrySettling) {
653
+ const settling = entrySettling;
654
+ try { await Promise.race([settling, exitPromise]); }
655
+ catch (error) {
656
+ if (error instanceof Error && error.message.startsWith('Process exited with code')) return { stdout, stderr, exitCode };
657
+ const errorMsg = error instanceof Error ? `${error.message}\n${error.stack || ''}` : String(error);
658
+ return { stdout, stderr: stderr + `Error: ${errorMsg}\n`, exitCode: 1 };
659
+ }
660
+ if (exitCalled) { settling.catch(() => {}); return { stdout, stderr, exitCode }; }
661
+ }
662
+
663
+ // Script returned without calling process.exit().
664
+ // Heuristic: if we already captured output, the script likely finished synchronously
665
+ // (e.g. a simple "console.log('hello')" script). Return immediately.
666
+ // A program that printed and still holds a timer, or a build, is working:
667
+ // the timers are the engine's own count for this guest, where this asked
668
+ // an adapter that is gone through an ambient name nothing ever installed.
669
+ // An active handle keeps Node's loop alive, so a run that owns one is not
670
+ // idle however long it has been quiet. The engine registers a guest's
671
+ // servers under this run's name as it opens them, and releases them when
672
+ // the run ends; counting them here is what a `vite` a guest spawned needs,
673
+ // which listened, printed, and was cut half a second later with its server
674
+ // still up. A connected socket is such a handle too, and counting only the
675
+ // listening ones settled VS Code's extension host — a program whose only
676
+ // handle is one socket back to the server that forked it and which sets no
677
+ // timer — as idle, with exit 0, three times over.
678
+ const __ownsHandles = () => runToken !== null && (__ownedServerPorts(runToken).length > 0 || __ownedHandleCount(runToken) > 0);
679
+ const __printedThenWorking = () => pendingGuestTimers(proc) > 0 || heldWork().count > 0 || __ownsHandles();
680
+ if ((stdout.length > 0 || stderr.length > 0) && !__printedThenWorking()) {
681
+ // Settling the command is host work. Killing guest timers must not
682
+ // cancel this continuation and leave the command's promise unresolved.
683
+ await new Promise(r => (globalThis.__browserRuntimeNativeSetTimeout ?? setTimeout).call(globalThis, r, 0));
684
+ // Work that began during that tick is the program's still: a build
685
+ // reached through a dynamic import's chain, a timer the entry set.
686
+ // vue-pure-admin's mock loader, `import('bundle-import').then(...)`
687
+ // after one printed line, was cut here with exit 0 before its build
688
+ // began, where Node's loop runs until nothing is left. Such a program
689
+ // waits below with the others.
690
+ if (exitCalled) return { stdout, stderr, exitCode };
691
+ // A run a host holds under a signal is the host's to end: it reads the
692
+ // program's servers and work through doors of its own and keeps the
693
+ // program open by them. Such a run reports settled here, as it did,
694
+ // once nothing is held; holding it for a pending timer instead made
695
+ // the substrate's loop take a `vite` quiet during its start for a
696
+ // finished run and abort it before it listened. A run nobody holds
697
+ // waits for its timers below, as Node's loop does.
698
+ if (streams?.held || (!__printedThenWorking() && pendingGuestTimers(proc) === 0)) {
699
+ return { stdout, stderr, exitCode: typeof proc !== 'undefined' && typeof proc.exitCode === 'number' ? proc.exitCode : 0 };
700
+ }
701
+ }
702
+
703
+ // No output yet — script likely has async work (e.g. vitest test runner).
704
+ // Wait for process.exit() or until output stabilizes.
705
+
706
+ // Listen for forked child exits to shorten the idle timeout.
707
+ // Many CLI tools (vitest, jest, etc.) fork workers and exit shortly after
708
+ // all children complete. We use a shorter timeout once children are done.
709
+ let childrenExited = false;
710
+ const prevChildExitHandler = _onForkedChildExit;
711
+ _onForkedChildExit = () => {
712
+ if (_activeForkedChildren <= 0) childrenExited = true;
713
+ prevChildExitHandler?.();
714
+ };
715
+
716
+ try {
717
+ // Poll until process.exit is called, output stabilizes, or we time out
718
+ const MAX_TOTAL_MS = 60000;
719
+ const IDLE_TIMEOUT_MS = 500;
720
+ const SILENT_IDLE_TIMEOUT_MS = 2000;
721
+ const POST_CHILD_EXIT_IDLE_MS = 100; // short timeout after children finish
722
+ const CHECK_MS = 50;
723
+ const startTime = Date.now();
724
+ let lastOutputLen = stdout.length + stderr.length;
725
+ let idleMs = 0;
726
+ // A timer the guest still holds is work in Node's loop, whether or not
727
+ // the program has printed; so is a handle it has open.
728
+ const stillWorking = (): boolean => pendingGuestTimers(proc) > 0 || __ownsHandles();
729
+
730
+ // When an abort signal is present (e.g. watch mode), don't apply idle timeout —
731
+ // only exit when aborted or process.exit is called.
732
+ const isLongRunning = !!streams?.held;
733
+
734
+ while (!exitCalled) {
735
+ // Check abort signal for long-running commands (watch mode)
736
+ if (streams?.signal?.aborted) break;
737
+
738
+ // Check if exitPromise resolved (non-blocking)
739
+ const raceResult = await Promise.race([
740
+ exitPromise.then(() => 'exit' as const),
741
+ new Promise<'tick'>(r => (globalThis.__browserRuntimeNativeSetTimeout ?? setTimeout).call(globalThis, () => r('tick'), CHECK_MS)),
742
+ ]);
743
+
744
+ if (raceResult === 'exit' || exitCalled) break;
745
+ if (streams?.signal?.aborted) break;
746
+
747
+ const currentLen = stdout.length + stderr.length;
748
+ if (currentLen > lastOutputLen) {
749
+ // New output — reset idle timer
750
+ lastOutputLen = currentLen;
751
+ idleMs = 0;
752
+ } else {
753
+ idleMs += CHECK_MS;
754
+ }
755
+
756
+ // Use shorter idle timeout once all forked children have exited
757
+ // Skip idle timeout for long-running commands (watch mode)
758
+ if (!isLongRunning) {
759
+ const effectiveIdle = childrenExited ? POST_CHILD_EXIT_IDLE_MS : IDLE_TIMEOUT_MS;
760
+ if (heldWork().count > 0) idleMs = 0;
761
+ // A program ends when its loop has nothing left, in Node whether or
762
+ // not it printed. The break waited for output, so a program that
763
+ // wrote nothing, a passing test of Node's own suite, waited the
764
+ // whole minute. A silent program that has been idle for a while,
765
+ // with no build held and no timer of its own pending, is done; the
766
+ // longer wait is for a start that is quiet while it fetches, which
767
+ // the engine cannot yet see as work.
768
+ const silentIdle = SILENT_IDLE_TIMEOUT_MS;
769
+ if (lastOutputLen > 0 && !stillWorking() && idleMs >= effectiveIdle) break;
770
+ if (lastOutputLen === 0 && !stillWorking() && idleMs >= silentIdle) break;
771
+ }
772
+
773
+ // The hard timeout is for a program whose work the engine cannot see:
774
+ // a start that is quiet while it fetches, a loop with nothing in the
775
+ // engine's own registries. A run that still holds a handle or a timer
776
+ // is not that -- Node's loop runs while one is open, for as long as it
777
+ // is open -- and cutting it at a minute ended a forked child that was
778
+ // a server, which is every extension host and every pty host.
779
+ if (!isLongRunning && !stillWorking() && Date.now() - startTime >= MAX_TOTAL_MS) break;
780
+ }
781
+
782
+ return { stdout, stderr, exitCode: exitCalled ? exitCode : (typeof proc !== 'undefined' && typeof proc.exitCode === 'number' ? proc.exitCode : 0) };
783
+ } finally {
784
+ if (streams) streams.stdin = null;
785
+ _onForkedChildExit = prevChildExitHandler;
786
+ }
787
+ } finally {
788
+ detachRejections();
789
+ detachUncaught();
790
+ // A process that ends releases its listening sockets, whatever ended it:
791
+ // a return from the entry, a throw, an exit. Only `process.exit` released
792
+ // them here, so `vite --host` — which opened 5173 and then died at startup
793
+ // with an ESLint error, exit 1 — left 5173 in the registry with nothing
794
+ // behind it, and the page went on previewing a port no process held.
795
+ // Safe twice: a run that exited already emptied its own entries.
796
+ if (runToken !== null) { __releaseOwnedServers(runToken, false); __releaseOwnedHandles(runToken); }
797
+ // A run that has ended is no longer a process: the number it wrote into
798
+ // a lock file answers ESRCH from here on, which is how a stale lock is
799
+ // stolen rather than waited on.
800
+ if (runToken !== null) forgetRunPid(runToken);
801
+ // A program that has ended holds no timers, as an ended Node process
802
+ // holds none. `process.exit` asked for them to be stopped on a tick of
803
+ // its own, and the run was taken out of the registry here before that
804
+ // tick came, so the stop found nothing: an `setInterval` whose callback
805
+ // threw ended its program and then went on firing forever, into a run
806
+ // that no longer had anywhere to report an exception, and out to the
807
+ // realm. The run's own timers stop here, while the process is still in
808
+ // hand; the scheduled stop then finds nothing left to do.
809
+ stopGuestTimers(proc);
810
+ releaseRun?.();
811
+ // A run that has ended owns nothing more: a server a later, unnamed run
812
+ // opens is nobody's, not the last named run's.
813
+ if (runToken !== null && __lastLaunchedToken === runToken) __setLastLaunchedToken(launchedBefore);
814
+ }
815
+ });
816
+
817
+ // Create custom 'npm' command that runs scripts from package.json
818
+ const npmCommand = defineCommand('npm', async (args, ctx) => {
819
+ if (!tree) {
820
+ return { stdout: '', stderr: 'VFS not initialized\n', exitCode: 1 };
821
+ }
822
+
823
+ const subcommand = args[0];
824
+
825
+ if (!subcommand || subcommand === 'help' || subcommand === '--help') {
826
+ return {
827
+ stdout: 'Usage: npm <command>\n\nCommands:\n run <script> Run a script from package.json\n start Run the start script\n test Run the test script\n install [pkg] Install packages\n ls List installed packages\n',
828
+ stderr: '',
829
+ exitCode: 0,
830
+ };
831
+ }
832
+
833
+ switch (subcommand) {
834
+ case 'run':
835
+ case 'run-script':
836
+ return handleNpmRun(args.slice(1), ctx);
837
+ case 'start':
838
+ return handleNpmRun(['start'], ctx);
839
+ case 'test':
840
+ case 't':
841
+ case 'tst':
842
+ return handleNpmRun(['test'], ctx);
843
+ case 'install':
844
+ case 'i':
845
+ case 'add':
846
+ return handleNpmInstall(args.slice(1), ctx);
847
+ case 'ls':
848
+ case 'list':
849
+ return handleNpmList(ctx);
850
+ default:
851
+ return {
852
+ stdout: '',
853
+ stderr: `npm ERR! Unknown command: "${subcommand}"\n`,
854
+ exitCode: 1,
855
+ };
856
+ }
857
+ });
858
+
859
+ bashInstance = new Bash({
860
+ fs: vfsAdapter,
861
+ cwd: '/',
862
+ env: {
863
+ HOME: '/home/user',
864
+ USER: 'user',
865
+ PATH: '/usr/local/bin:/usr/bin:/bin:/node_modules/.bin',
866
+ },
867
+ customCommands: [nodeCommand, npmCommand],
868
+ });
869
+ shells.set(tree, { bash: bashInstance, adapter: vfsAdapter });
870
+
871
+ if (processRunnerInstalled) return;
872
+ processRunnerInstalled = true;
873
+
874
+ // What a `Process` handle runs. Node's own `child_process.js` starts, ends
875
+ // and pipes a child through the binding; the binding asks this.
876
+ setProgramResolver(engineProgramFor);
877
+ setProcessRunner({
878
+ resolves: (request: RunRequest): boolean => {
879
+ if (!bashInstance) return false;
880
+ // A directory that is not there is `ENOENT` before the program is even
881
+ // looked for, as it is for `execve`: `spawn(cmd, { cwd: 'nope' })`
882
+ // reports a child that never started, and `child.pid` is undefined.
883
+ if (typeof request.cwd === 'string' && request.cwd.length > 0 && !existsInTree(request.cwd)) return false;
884
+ // A `{ shell: true }` spawn is `/bin/sh -c <line>`, and the shell is
885
+ // always there; what the line names is the line's own business, as it
886
+ // is on a machine.
887
+ if (__substrateShellLine(request.file, request.args) !== null) return true;
888
+ // A page that published a process host carries programs the engine
889
+ // cannot enumerate -- a registered command, a WALI pack -- so the host
890
+ // is asked by running it, and reports its own absence.
891
+ if (hostExecutor() !== null) return true;
892
+ return programExists(request.file, request.cwd, request.env);
893
+ },
894
+ start: startChildRun,
895
+ });
896
+ watchChildren();
897
+ }
898
+
899
+ /**
900
+ * The child's end of an IPC channel, wired onto its own process before its
901
+ * module runs. This is `lib/internal/process/pre_execution.js`'s
902
+ * `setupChildProcessIpcChannel` and `child_process._forkChild`'s body, over
903
+ * the engine's own process object rather than the realm's `process`, because
904
+ * a realm here holds many programs and the channel belongs to one of them.
905
+ */
906
+ function attachChannel(proc: ReturnType<Runtime['getProcess']>, fd: number, serialization: string): void {
907
+ const channel = new Pipe(pipeConstants.IPC);
908
+ if (channel.open(fd) !== 0) { channel.close(); return; }
909
+ // Node unrefs a child's channel: the channel alone does not hold a program
910
+ // open, and `Control.refCounted` puts it back the moment the program
911
+ // listens for a message.
912
+ channel.unref();
913
+ const control = setupChannel(proc, channel, serialization);
914
+ proc.on('newListener', (name: unknown) => {
915
+ if (name === 'message' || name === 'disconnect') control.refCounted();
916
+ });
917
+ proc.on('removeListener', (name: unknown) => {
918
+ if (name === 'message' || name === 'disconnect') control.unrefCounted();
919
+ });
920
+ }
921
+
922
+ /** The shell's environment as a record: just-bash hands a command a Map. */
923
+ function environmentOf(ctx: CommandContext): Record<string, string> {
924
+ const env: unknown = ctx.env;
925
+ if (env instanceof Map) return Object.fromEntries(env as Map<string, string>);
926
+ return { ...((env ?? {}) as Record<string, string>) };
927
+ }
928
+
929
+ /**
930
+ * The environment a guest process sees. The name its run travels under is the
931
+ * shell's business, not the program's: Node's `process.env` carries no name
932
+ * for the process, so the variable is taken out here while the shell keeps it
933
+ * for a `node` a script of its own goes on to run.
934
+ */
935
+ function guestEnvironmentOf(ctx: CommandContext): Record<string, string> {
936
+ const env = environmentOf(ctx);
937
+ delete env[PROCESS_TOKEN_ENV];
938
+ return env;
939
+ }
940
+
941
+ /** The name the host gave the run this command belongs to, where it named one. */
942
+ function runTokenOf(ctx: CommandContext): ProcessToken | null {
943
+ const value = ctx.env instanceof Map ? ctx.env.get(PROCESS_TOKEN_ENV) : (ctx.env as Record<string, string> | undefined)?.[PROCESS_TOKEN_ENV];
944
+ return typeof value === 'string' && value.length > 0 ? value : null;
945
+ }
946
+
947
+ /**
948
+ * Read and parse package.json from the VFS
949
+ */
950
+ function readPackageJson(cwd: string): { pkgJson: PackageJson; error?: undefined } | { pkgJson?: undefined; error: JustBashExecResult } {
951
+ const pkgJsonPath = `${cwd}/package.json`.replace(/\/+/g, '/');
952
+
953
+ if (!currentVfs!.existsSync(pkgJsonPath)) {
954
+ return {
955
+ error: {
956
+ stdout: '',
957
+ stderr: 'npm ERR! no package.json found\n',
958
+ exitCode: 1,
959
+ },
960
+ };
961
+ }
962
+
963
+ try {
964
+ const pkgJson = JSON.parse(currentVfs!.readFileSync(pkgJsonPath, 'utf8')) as PackageJson;
965
+ return { pkgJson };
966
+ } catch {
967
+ return {
968
+ error: {
969
+ stdout: '',
970
+ stderr: 'npm ERR! Failed to parse package.json\n',
971
+ exitCode: 1,
972
+ },
973
+ };
974
+ }
975
+ }
976
+
977
+ /**
978
+ * Handle `npm run [script]` — execute a script from package.json
979
+ */
980
+ async function handleNpmRun(args: string[], ctx: CommandContext): Promise<JustBashExecResult> {
981
+ const scriptName = args[0];
982
+
983
+ // "npm run" with no script name: list available scripts
984
+ if (!scriptName) {
985
+ return listScripts(ctx);
986
+ }
987
+
988
+ const result = readPackageJson(ctx.cwd);
989
+ if (result.error) return result.error;
990
+ const pkgJson = result.pkgJson;
991
+
992
+ const scripts = pkgJson.scripts || {};
993
+ const scriptCommand = scripts[scriptName];
994
+
995
+ if (!scriptCommand) {
996
+ const available = Object.keys(scripts);
997
+ let msg = `npm ERR! Missing script: "${scriptName}"\n`;
998
+ if (available.length > 0) {
999
+ msg += '\nnpm ERR! Available scripts:\n';
1000
+ for (const name of available) {
1001
+ msg += `npm ERR! ${name}\n`;
1002
+ msg += `npm ERR! ${scripts[name]}\n`;
1003
+ }
1004
+ }
1005
+ return { stdout: '', stderr: msg, exitCode: 1 };
1006
+ }
1007
+
1008
+ if (!ctx.exec) {
1009
+ return {
1010
+ stdout: '',
1011
+ stderr: 'npm ERR! Script execution not available in this context\n',
1012
+ exitCode: 1,
1013
+ };
1014
+ }
1015
+
1016
+ // Set up npm-specific environment variables
1017
+ const npmEnv: Record<string, string> = {
1018
+ ...environmentOf(ctx),
1019
+ npm_lifecycle_event: scriptName,
1020
+ };
1021
+ if (pkgJson.name) npmEnv.npm_package_name = pkgJson.name;
1022
+ if (pkgJson.version) npmEnv.npm_package_version = pkgJson.version;
1023
+
1024
+ let allStdout = '';
1025
+ let allStderr = '';
1026
+ const label = `${pkgJson.name || ''}@${pkgJson.version || ''}`;
1027
+
1028
+ // Run pre<script> if it exists
1029
+ const preScript = scripts[`pre${scriptName}`];
1030
+ if (preScript) {
1031
+ allStderr += `\n> ${label} pre${scriptName}\n> ${preScript}\n\n`;
1032
+ const preResult = await ctx.exec(preScript, { cwd: ctx.cwd, env: npmEnv });
1033
+ allStdout += preResult.stdout;
1034
+ allStderr += preResult.stderr;
1035
+ if (preResult.exitCode !== 0) {
1036
+ return { stdout: allStdout, stderr: allStderr, exitCode: preResult.exitCode };
1037
+ }
1038
+ }
1039
+
1040
+ // Run the main script
1041
+ allStderr += `\n> ${label} ${scriptName}\n> ${scriptCommand}\n\n`;
1042
+ const mainResult = await ctx.exec(scriptCommand, { cwd: ctx.cwd, env: npmEnv });
1043
+ allStdout += mainResult.stdout;
1044
+ allStderr += mainResult.stderr;
1045
+
1046
+ if (mainResult.exitCode !== 0) {
1047
+ return { stdout: allStdout, stderr: allStderr, exitCode: mainResult.exitCode };
1048
+ }
1049
+
1050
+ // Run post<script> if it exists
1051
+ const postScript = scripts[`post${scriptName}`];
1052
+ if (postScript) {
1053
+ allStderr += `\n> ${label} post${scriptName}\n> ${postScript}\n\n`;
1054
+ const postResult = await ctx.exec(postScript, { cwd: ctx.cwd, env: npmEnv });
1055
+ allStdout += postResult.stdout;
1056
+ allStderr += postResult.stderr;
1057
+ if (postResult.exitCode !== 0) {
1058
+ return { stdout: allStdout, stderr: allStderr, exitCode: postResult.exitCode };
1059
+ }
1060
+ }
1061
+
1062
+ return { stdout: allStdout, stderr: allStderr, exitCode: 0 };
1063
+ }
1064
+
1065
+ /**
1066
+ * List available scripts from package.json (when `npm run` is called with no args)
1067
+ */
1068
+ function listScripts(ctx: CommandContext): JustBashExecResult {
1069
+ const result = readPackageJson(ctx.cwd);
1070
+ if (result.error) return result.error;
1071
+ const pkgJson = result.pkgJson;
1072
+
1073
+ const scripts = pkgJson.scripts || {};
1074
+ const names = Object.keys(scripts);
1075
+
1076
+ if (names.length === 0) {
1077
+ return { stdout: '', stderr: '', exitCode: 0 };
1078
+ }
1079
+
1080
+ const lifecycle = ['prestart', 'start', 'poststart', 'pretest', 'test', 'posttest', 'prestop', 'stop', 'poststop'];
1081
+ const lifecyclePresent = names.filter(n => lifecycle.includes(n));
1082
+ const customPresent = names.filter(n => !lifecycle.includes(n));
1083
+
1084
+ let output = `Lifecycle scripts included in ${pkgJson.name || ''}:\n`;
1085
+ for (const name of lifecyclePresent) {
1086
+ output += ` ${name}\n ${scripts[name]}\n`;
1087
+ }
1088
+ if (customPresent.length > 0) {
1089
+ output += '\navailable via `npm run-script`:\n';
1090
+ for (const name of customPresent) {
1091
+ output += ` ${name}\n ${scripts[name]}\n`;
1092
+ }
1093
+ }
1094
+
1095
+ return { stdout: output, stderr: '', exitCode: 0 };
1096
+ }
1097
+
1098
+ /**
1099
+ * Handle `npm install [pkg]` — bridge to PackageManager
1100
+ */
1101
+ async function handleNpmInstall(args: string[], ctx: CommandContext): Promise<JustBashExecResult> {
1102
+ const { PackageManager } = await import('../npm/index');
1103
+ const pm = new PackageManager(currentVfs!, { cwd: ctx.cwd });
1104
+
1105
+ let stdout = '';
1106
+
1107
+ try {
1108
+ const pkgArgs = args.filter(a => !a.startsWith('-'));
1109
+ if (pkgArgs.length === 0) {
1110
+ // npm install (no package name) -> install from package.json.
1111
+ // npm installs devDependencies unless told not to: `--omit=dev`,
1112
+ // `--production`, `--prod`, or `NODE_ENV=production`. A bare install that
1113
+ // left them out made a project whose build script runs `tsc` or `tsup`
1114
+ // install cleanly and then find no such command.
1115
+ const __omitDev = args.includes('--omit=dev') || args.includes('--production') || args.includes('--prod') || (environmentOf(ctx).NODE_ENV === 'production');
1116
+ const installResult = await pm.installFromPackageJson({
1117
+ includeDev: !__omitDev,
1118
+ onProgress: (msg: string) => { stdout += msg + '\n'; },
1119
+ });
1120
+ stdout += `added ${installResult.added.length} packages\n`;
1121
+ } else {
1122
+ // npm install <pkg> [<pkg> ...]
1123
+ for (const arg of pkgArgs) {
1124
+ const installResult = await pm.install(arg, {
1125
+ save: true,
1126
+ onProgress: (msg: string) => { stdout += msg + '\n'; },
1127
+ });
1128
+ stdout += `added ${installResult.added.length} packages\n`;
1129
+ }
1130
+ }
1131
+ return { stdout, stderr: '', exitCode: 0 };
1132
+ } catch (error) {
1133
+ const msg = error instanceof Error ? error.stack || error.message : String(error);
1134
+ return { stdout, stderr: `npm ERR! ${msg}\n`, exitCode: 1 };
1135
+ }
1136
+ }
1137
+
1138
+ /**
1139
+ * Handle `npm ls` — list installed packages
1140
+ */
1141
+ async function handleNpmList(ctx: CommandContext): Promise<JustBashExecResult> {
1142
+ const { PackageManager } = await import('../npm/index');
1143
+ const pm = new PackageManager(currentVfs!, { cwd: ctx.cwd });
1144
+ const packages = pm.list();
1145
+ const entries = Object.entries(packages);
1146
+
1147
+ if (entries.length === 0) {
1148
+ return { stdout: '(empty)\n', stderr: '', exitCode: 0 };
1149
+ }
1150
+
1151
+ let output = `${ctx.cwd}\n`;
1152
+ for (const [name, version] of entries) {
1153
+ output += `+-- ${name}@${version}\n`;
1154
+ }
1155
+ return { stdout: output, stderr: '', exitCode: 0 };
1156
+ }
1157
+
1158
+ /** What a host asks of `runCommand`, the engine's own door onto its shell. */
1159
+ export interface ExecOptions {
1160
+ cwd?: string;
1161
+ env?: Record<string, string>;
1162
+ /** What the shell reads on stdin, so a builtin reads what was piped to it. */
1163
+ stdin?: string;
1164
+ /**
1165
+ * A name for this run, which the `node` command records its guest process
1166
+ * under and the container answers `pendingTimers`, `processPorts` and
1167
+ * `stopProcess` about. It travels in the shell's environment so that two
1168
+ * runs in flight at once each reach their own command.
1169
+ */
1170
+ processToken?: string;
1171
+ /** The tree this command runs on, so two containers in one realm keep theirs. */
1172
+ vfs?: VirtualFS;
1173
+ }
1174
+
1175
+ /** What `runCommand` answers with, in the shape a shell answers. */
1176
+ export type ExecCallback = (
1177
+ error: (Error & { code?: number }) | null,
1178
+ stdout: string,
1179
+ stderr: string
1180
+ ) => void;
1181
+
1182
+ /**
1183
+ * The one host hook Tabnode lacks: a child process can execute through the
1184
+ * embedding runtime's shared process host instead of this module's private
1185
+ * just-bash instance. The host publishes an executor on a well-known global
1186
+ * symbol; when none is published every call falls through to just-bash. This
1187
+ * is the door a page-registered program and a WALI pack are reached by, and
1188
+ * it is what a `Process` handle runs when the host has taken it.
1189
+ */
1190
+ /** What the embedding runtime's process host answers when a command finishes. */
1191
+ interface ChildProcessHostResult {
1192
+ stdout?: string;
1193
+ stderr?: string;
1194
+ exitCode: number;
1195
+ }
1196
+
1197
+ /** How a command is handed to that host. */
1198
+ interface ChildProcessHostRequest {
1199
+ cwd?: string;
1200
+ env?: Record<string, string>;
1201
+ /** Bytes already on fd 0 when the command begins, as Node delivers them. */
1202
+ stdin?: string;
1203
+ stdinStream: AsyncIterable<Uint8Array>;
1204
+ signal: AbortSignal;
1205
+ hold: { value: boolean };
1206
+ onStdout: (data: string) => void;
1207
+ onStderr: (data: string) => void;
1208
+ }
1209
+
1210
+ /** The executor a host publishes on the well-known global symbol. */
1211
+ interface ChildProcessHost {
1212
+ run(command: string, request: ChildProcessHostRequest): Promise<ChildProcessHostResult>;
1213
+ /** Runs the host leaves to just-bash instead, one per count. */
1214
+ bypass: number;
1215
+ parentSignal?: AbortSignal;
1216
+ }
1217
+
1218
+ const __browserRuntimeChildProcessExecutorSymbol = Symbol.for("@volter/browser-runtime/child-process-executor");
1219
+
1220
+ /** The host's executor, where one is published and has not bypassed this run. */
1221
+ function hostExecutor(): ChildProcessHost | null {
1222
+ const bridge = (globalThis as unknown as Record<symbol, ChildProcessHost | undefined>)[__browserRuntimeChildProcessExecutorSymbol];
1223
+ if (!bridge || typeof bridge.run !== 'function') return null;
1224
+ return bridge;
1225
+ }
1226
+
1227
+ /** What a run of a command answers, however it was routed. */
1228
+ interface CommandOutcome {
1229
+ stdout: string;
1230
+ stderr: string;
1231
+ exitCode: number;
1232
+ }
1233
+
1234
+ /** What a caller gives one run of a command line. */
1235
+ interface CommandRun {
1236
+ command: string;
1237
+ /**
1238
+ * Whether the engine's own shell answers for this command before the host's
1239
+ * process host is asked. A run the HOST asked for is the host's first: that
1240
+ * is how a page-registered program and a WALI pack are reached. A child a
1241
+ * GUEST spawned is the engine's first, because the engine has a command for
1242
+ * it and because a host that takes the child over reports it as a run of its
1243
+ * own -- which is why every line a forked child printed reached the tab's
1244
+ * terminal twice, once as the child's run and once down its parent's
1245
+ * inherited stdout. The host is still asked for what the engine does not
1246
+ * carry.
1247
+ */
1248
+ engineFirst?: boolean;
1249
+ cwd?: string;
1250
+ env?: Record<string, string>;
1251
+ stdin?: string;
1252
+ /** Bytes a writer still holds the run's fd 0 open with, as they arrive. */
1253
+ stdinStream?: AsyncIterable<Uint8Array>;
1254
+ signal?: AbortSignal;
1255
+ onStdout?: (data: string) => void;
1256
+ onStderr?: (data: string) => void;
1257
+ vfs?: VirtualFS;
1258
+ }
1259
+
1260
+ /**
1261
+ * One command line, run by whichever door answers for it: the host's own
1262
+ * process host where the page published one (a registered program, a WALI
1263
+ * pack), else the engine's shell, whose `node` and `npm` are its own commands.
1264
+ * This is the whole of the engine's routing, and it is what a `Process`
1265
+ * handle, a `runCommand` and a synchronous child all reach the world through.
1266
+ */
1267
+ async function routeCommand(run: CommandRun): Promise<CommandOutcome> {
1268
+ const tree = run.vfs ?? treeForRun();
1269
+ const shell = shellOf(tree);
1270
+ if (tree && shell) {
1271
+ currentVfs = tree;
1272
+ bashInstance = shell.bash;
1273
+ vfsAdapter = shell.adapter;
1274
+ }
1275
+ const bash = shell?.bash ?? bashInstance;
1276
+ const bridge = run.engineFirst === true && bash !== null ? null : hostExecutor();
1277
+ if (bridge !== null && bridge.bypass > 0) bridge.bypass -= 1;
1278
+ else if (bridge !== null) {
1279
+ const controller = new AbortController();
1280
+ const abortWithParent = (): void => {
1281
+ if (!controller.signal.aborted) controller.abort(bridge.parentSignal?.reason);
1282
+ };
1283
+ if (run.signal) {
1284
+ if (run.signal.aborted) controller.abort(run.signal.reason);
1285
+ else run.signal.addEventListener('abort', () => controller.abort(run.signal?.reason), { once: true });
1286
+ }
1287
+ if (bridge.parentSignal) {
1288
+ if (bridge.parentSignal.aborted) abortWithParent();
1289
+ else bridge.parentSignal.addEventListener('abort', abortWithParent, { once: true });
1290
+ }
1291
+ let streamedOut = '';
1292
+ let streamedErr = '';
1293
+ const onStdout = (data: string): void => { streamedOut += String(data); run.onStdout?.(String(data)); };
1294
+ const onStderr = (data: string): void => { streamedErr += String(data); run.onStderr?.(String(data)); };
1295
+ try {
1296
+ const result = await bridge.run(run.command, {
1297
+ cwd: run.cwd,
1298
+ env: run.env,
1299
+ ...(typeof run.stdin === 'string' ? { stdin: run.stdin } : {}),
1300
+ stdinStream: run.stdinStream ?? emptyStdinStream(),
1301
+ signal: controller.signal,
1302
+ hold: { value: true },
1303
+ onStdout,
1304
+ onStderr,
1305
+ });
1306
+ const stdout = result.stdout || streamedOut;
1307
+ const stderr = result.stderr || streamedErr;
1308
+ if (!streamedOut && result.stdout) run.onStdout?.(result.stdout);
1309
+ if (!streamedErr && result.stderr) run.onStderr?.(result.stderr);
1310
+ return { stdout, stderr, exitCode: result.exitCode };
1311
+ } finally {
1312
+ if (bridge.parentSignal) bridge.parentSignal.removeEventListener('abort', abortWithParent);
1313
+ }
1314
+ }
1315
+
1316
+ if (!bash) throw new Error('child_process not initialized');
1317
+ const result = await bash.exec(run.command, {
1318
+ cwd: run.cwd,
1319
+ env: run.env,
1320
+ ...(typeof run.stdin === 'string' ? { stdin: run.stdin } : {}),
1321
+ });
1322
+ return { stdout: result.stdout || '', stderr: result.stderr || '', exitCode: result.exitCode };
1323
+ }
1324
+
1325
+ /** A child whose stdin nobody writes to still needs a stream the host can read. */
1326
+ function emptyStdinStream(): AsyncIterable<Uint8Array> {
1327
+ return { async *[Symbol.asyncIterator]() { /* nothing is ever written */ } };
1328
+ }
1329
+
1330
+ /**
1331
+ * The host's door onto the engine's shell: one named run of a command line,
1332
+ * with the host's streams and its abort handle. This is `container.run`'s
1333
+ * half, not Node's `child_process.exec` -- Node's is its own file's, over the
1334
+ * binding, and it knows nothing of a run's name.
1335
+ */
1336
+ export function runCommand(command: string, options: ExecOptions, callback?: ExecCallback): void {
1337
+ void (async () => {
1338
+ try {
1339
+ const outcome = await routeCommand({
1340
+ command,
1341
+ cwd: options.cwd,
1342
+ env: typeof options.processToken === 'string'
1343
+ ? { ...(options.env ?? {}), [PROCESS_TOKEN_ENV]: options.processToken }
1344
+ : options.env,
1345
+ stdin: options.stdin,
1346
+ signal: typeof options.processToken === 'string' ? _runStreams.get(options.processToken)?.signal : undefined,
1347
+ vfs: options.vfs,
1348
+ });
1349
+ if (!callback) return;
1350
+ if (outcome.exitCode !== 0) {
1351
+ const error = Object.assign(new Error(`Command failed: ${command}`), { code: outcome.exitCode });
1352
+ callback(error, outcome.stdout, outcome.stderr);
1353
+ } else {
1354
+ callback(null, outcome.stdout, outcome.stderr);
1355
+ }
1356
+ } catch (error) {
1357
+ callback?.(error as Error & { code?: number }, '', '');
1358
+ }
1359
+ })();
1360
+ }
1361
+
1362
+ /**
1363
+ * The engine's own names for the programs it carries, for the one question
1364
+ * libuv answers before a child exists: is there such a program at all?
1365
+ * `spawn('nope')` is an `ENOENT` on the child, not a shell's 127 reported as
1366
+ * an exit status, and Node's `ChildProcess` can only tell the two apart if
1367
+ * `Process.spawn` says so when it is called.
1368
+ */
1369
+ let __shellCommandNames: Set<string> | null = null;
1370
+ function shellCommandNames(): Set<string> {
1371
+ if (__shellCommandNames === null) __shellCommandNames = new Set([...getCommandNames(), 'node', 'npm']);
1372
+ return __shellCommandNames;
1373
+ }
1374
+
1375
+ /** Whether a path names something the engine has. */
1376
+ function existsInTree(path: string): boolean {
1377
+ const tree = treeForRun();
1378
+ if (!tree) return false;
1379
+ try { return tree.existsSync(path); } catch { return false; }
1380
+ }
1381
+
1382
+ /**
1383
+ * The program the engine will actually run for the path a caller named.
1384
+ *
1385
+ * The engine's shell carries its commands under their plain names, and a
1386
+ * machine carries them at `/bin` and `/usr/bin`: `spawn('/usr/bin/env', …)`
1387
+ * means the shell's `env`, and that is what Node's own `test-child-process-env`
1388
+ * spawns. A file that really is in the tree at the path wins, because that is
1389
+ * a program the engine can run; the engine's own `node`, written to
1390
+ * `/usr/local/bin/node` when the shim is initialized, is one of those.
1391
+ */
1392
+ function engineProgramFor(file: string, cwd?: string): string {
1393
+ const name = __substrateProgramName(file);
1394
+ if (!name.includes('/')) return name;
1395
+ if (existsInTree(__resolvePath(cwd ?? '/', name))) return name;
1396
+ const bare = name.slice(name.lastIndexOf('/') + 1);
1397
+ return shellCommandNames().has(bare) ? bare : name;
1398
+ }
1399
+
1400
+ /** Whether the engine's shell can find a program under this name. */
1401
+ function programExists(file: string, cwd: string | undefined, env: Record<string, string>): boolean {
1402
+ const name = __substrateProgramName(file);
1403
+ const bare = name.includes('/') ? name.slice(name.lastIndexOf('/') + 1) : name;
1404
+ // The engine's shell carries its commands under their plain names and at
1405
+ // the paths a machine carries them at: `/usr/bin/env` is `env`, which is
1406
+ // what `spawn('/usr/bin/env', …)` means and what a Dockerfile's shebang
1407
+ // reaches. A file in the tree is a program too, and so is one on PATH.
1408
+ if (shellCommandNames().has(bare)) return true;
1409
+ if (name.includes('/')) return existsInTree(__resolvePath(cwd ?? '/', name));
1410
+ for (const dir of (env.PATH ?? '/usr/local/bin:/usr/bin:/bin:/node_modules/.bin').split(':')) {
1411
+ if (dir.length > 0 && existsInTree(`${dir}/${name}`.replace(/\/+/gu, '/'))) return true;
1412
+ }
1413
+ return false;
1414
+ }
1415
+
1416
+ /** The runs a `Process` handle started, so a later name never lands on a live one. */
1417
+ let __nextChildRun = 1;
1418
+
1419
+ /**
1420
+ * What a `Process` handle runs: a named run of the engine, with its streams
1421
+ * wired to the handle's pipes and its end reported once.
1422
+ *
1423
+ * A killed run reports its end at once. The engine has no signal to deliver:
1424
+ * a command already inside the shell cannot always be interrupted, and a
1425
+ * parent that called `kill` has no process left to wait for -- so the abort
1426
+ * goes out, the run's timers and handles are released as ending a process
1427
+ * releases them, and `onexit` carries the signal. Whatever the command does
1428
+ * afterwards is nobody's.
1429
+ */
1430
+ function startChildRun(request: RunRequest): StartedRun {
1431
+ const token: ProcessToken = `child-${__nextChildRun++}`;
1432
+ // A child is a process and has its own number, which its parent reads off
1433
+ // the handle and the child itself reports as `process.pid`; `ppid` is the
1434
+ // number of the run that spawned it. `src/process-tokens.ts` says what
1435
+ // reads them.
1436
+ const pid = mintPid();
1437
+ setRunPid(token, pid, runPid(__currentProcessToken() ?? __lastLaunchedToken)?.pid ?? 0);
1438
+ const controller = new AbortController();
1439
+ const pendingStdin: Array<Uint8Array | null> = [];
1440
+ let stdinSink: RunStdin | null = null;
1441
+ let finished = false;
1442
+ let started = false;
1443
+ let killedBy: string | null = null;
1444
+ /** What the parent wrote to the child's fd 0 before the command began. */
1445
+ const initialStdin: Uint8Array[] = [];
1446
+
1447
+ const flushStdin = (): void => {
1448
+ if (!stdinSink) return;
1449
+ while (pendingStdin.length > 0) stdinSink.push(pendingStdin.shift() as Uint8Array | null);
1450
+ };
1451
+
1452
+ let streamedOut = 0;
1453
+ let streamedErr = 0;
1454
+ const streams: RunStreams = {
1455
+ onStdout: (data: string) => { streamedOut += data.length; request.stdout?.(data); },
1456
+ onStderr: (data: string) => { streamedErr += data.length; request.stderr?.(data); },
1457
+ signal: controller.signal,
1458
+ held: false,
1459
+ stdinOpen: request.stdinIsPipe,
1460
+ stderrIsPipe: request.stderrIsPipe,
1461
+ get stdin(): RunStdin | null | undefined { return stdinSink; },
1462
+ set stdin(sink: RunStdin | null | undefined) { stdinSink = sink ?? null; if (stdinSink) flushStdin(); },
1463
+ };
1464
+ registerRunStreams(token, streams);
1465
+ // The child is started with the descriptors its parent wired: an IPC channel
1466
+ // is at the number its `NODE_CHANNEL_FD` will name, under this run's name,
1467
+ // because every child is told the same number.
1468
+ if (request.channel) registerRunFd(token, request.channel.fd, 'PIPE', request.channel.pipe);
1469
+
1470
+ const end = (code: number, signal: string | null): void => {
1471
+ if (finished) return;
1472
+ finished = true;
1473
+ _activeForkedChildren -= 1;
1474
+ releaseRunStreams(token);
1475
+ releaseRunFds(token);
1476
+ // A run that has ended is no longer a process: its number answers ESRCH.
1477
+ forgetRunPid(token);
1478
+ _onForkedChildExit?.();
1479
+ request.exit(code, signal);
1480
+ };
1481
+
1482
+ /**
1483
+ * The command begins here, once, with whatever its standard input already
1484
+ * holds. The engine's shell takes a run's standard input as one string
1485
+ * given before the command runs -- `cat` and every other builtin read
1486
+ * `ctx.stdin` and nothing after it -- so a child whose fd 0 is a pipe waits
1487
+ * for the turn in which it was spawned to finish: `spawn('cat')`,
1488
+ * `stdin.write(...)`, `stdin.end()` is one synchronous block in every
1489
+ * program that does it, and the writes have landed by the time the realm's
1490
+ * own loop comes back. A write after that reaches a `node` run, whose fd 0
1491
+ * is a real stream, and is lost on a builtin, which has already read.
1492
+ */
1493
+ const begin = (): void => {
1494
+ if (started || finished) return;
1495
+ started = true;
1496
+ const stdin = initialStdin.length > 0 ? Buffer.concat(initialStdin).toString('utf8') : undefined;
1497
+ void (async () => {
1498
+ let outcome: CommandOutcome;
1499
+ try {
1500
+ outcome = await routeCommand({
1501
+ command: __substrateLineFor(request.file, request.args, request.cwd),
1502
+ engineFirst: programExists(request.file, request.cwd, request.env),
1503
+ cwd: request.cwd,
1504
+ env: { ...request.env, [PROCESS_TOKEN_ENV]: token },
1505
+ stdin,
1506
+ signal: controller.signal,
1507
+ onStdout: streams.onStdout,
1508
+ onStderr: streams.onStderr,
1509
+ });
1510
+ } catch (error) {
1511
+ outcome = { stdout: '', stderr: `${error instanceof Error ? error.message : String(error)}\n`, exitCode: 1 };
1512
+ }
1513
+ // A command the engine's shell ran to the end and did not stream --
1514
+ // every builtin -- delivers its output here; the `node` command streamed
1515
+ // its own as it went, and only what it has not already sent is left.
1516
+ if (outcome.stdout.length > streamedOut) request.stdout?.(outcome.stdout.slice(streamedOut));
1517
+ if (outcome.stderr.length > streamedErr) request.stderr?.(outcome.stderr.slice(streamedErr));
1518
+ end(outcome.exitCode, null);
1519
+ })();
1520
+ };
1521
+
1522
+ _activeForkedChildren += 1;
1523
+ if (request.stdinIsPipe) (globalThis.__browserRuntimeNativeSetTimeout ?? setTimeout).call(globalThis, begin, 0);
1524
+ else begin();
1525
+
1526
+ return {
1527
+ token,
1528
+ pid,
1529
+ kill(signal: string): number {
1530
+ if (finished) return UV_ESRCH;
1531
+ killedBy = signal;
1532
+ controller.abort(signal);
1533
+ __releaseOwnedServers(token, false);
1534
+ __releaseOwnedHandles(token);
1535
+ __stopOwnedProcess(token);
1536
+ end(0, killedBy);
1537
+ return 0;
1538
+ },
1539
+ writeStdin(bytes: Uint8Array): void {
1540
+ if (!started) { initialStdin.push(bytes); return; }
1541
+ pendingStdin.push(bytes);
1542
+ flushStdin();
1543
+ },
1544
+ endStdin(): void {
1545
+ if (!started) {
1546
+ // The writer closed before the command began: the run's fd 0 is the
1547
+ // string already collected and nothing more, so it is not held open.
1548
+ streams.stdinOpen = false;
1549
+ begin();
1550
+ return;
1551
+ }
1552
+ pendingStdin.push(null);
1553
+ flushStdin();
1554
+ },
1555
+ };
1556
+ }
1557
+
1558
+ /**
1559
+ * The guest's children are processes it can see, so `process.kill(pid, 0)`
1560
+ * answers about a live child rather than ESRCH for every pid but its own.
1561
+ * Node publishes each `ChildProcess` on the `child_process` diagnostics
1562
+ * channel as it is constructed, which is the door this reads: the pid is
1563
+ * filled by `spawn` a moment later, so the child is recorded on the next tick.
1564
+ */
1565
+ function watchChildren(): void {
1566
+ // Node's own `diagnostics_channel`, the same module the vendored
1567
+ // `child_process.js` publishes on. A channel is a name in one registry, and
1568
+ // the engine's own imitation of the module was a second registry: a
1569
+ // subscriber there heard nothing a vendored file published.
1570
+ const diagnosticsChannel = diagnosticsChannelModule as unknown as {
1571
+ channel(name: string): { subscribe(listener: (message: unknown) => void): void };
1572
+ };
1573
+ diagnosticsChannel.channel('child_process').subscribe((message: unknown) => {
1574
+ const child = (message as { process?: { pid?: number; exitCode: number | null; signalCode: string | null; kill(signal?: string): boolean; once(event: string, listener: () => void): unknown } }).process;
1575
+ if (!child) return;
1576
+ queueMicrotask(() => {
1577
+ const pid = child.pid;
1578
+ if (typeof pid !== 'number' || pid === 0) return;
1579
+ __substrateChildren.set(pid, child);
1580
+ child.once('exit', () => { __substrateChildren.delete(pid); });
1581
+ });
1582
+ });
1583
+ }
1584
+
1585
+ export default {
1586
+ initChildProcess,
1587
+ registerRunStreams,
1588
+ releaseRunStreams,
1589
+ runCommand,
1590
+ sendStdin,
1591
+ };